mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-27 14:59:29 +01:00
feat: implement spaces hierarchy
- implement spaces hierarchy API - display suggested rooms below room list - allow joining suggested rooms Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
parent
014c1574ee
commit
26484cc2bd
@ -2763,14 +2763,21 @@
|
||||
"emailOrUsername": "Email or username",
|
||||
"@emailOrUsername": {},
|
||||
"switchToAccount": "Switch to account {number}",
|
||||
"@switchToAccount": {
|
||||
"type": "number",
|
||||
"placeholders": {
|
||||
"number": {}
|
||||
}
|
||||
},
|
||||
"nextAccount": "Next account",
|
||||
"previousAccount": "Previous account",
|
||||
"@switchToAccount": {
|
||||
"type": "number",
|
||||
"placeholders": {
|
||||
"number": {}
|
||||
}
|
||||
},
|
||||
"numberRoomMembers": "{number} members",
|
||||
"@numberRoomMembers": {
|
||||
"type": "number",
|
||||
"placeholders": {
|
||||
"number": {}
|
||||
}
|
||||
},
|
||||
"nextAccount": "Next account",
|
||||
"previousAccount": "Previous account",
|
||||
"editWidgets": "Edit widgets",
|
||||
"addWidget": "Add widget",
|
||||
"widgetVideo": "Video",
|
||||
|
59
lib/msc/extension_spaces_advanced/spaces_advaced.dart
Normal file
59
lib/msc/extension_spaces_advanced/spaces_advaced.dart
Normal file
@ -0,0 +1,59 @@
|
||||
library spaces_advanced;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_api_lite/matrix_api_lite.dart';
|
||||
|
||||
import 'src/space_hierarchy_model.dart';
|
||||
|
||||
extension SpacesAdvanced on MatrixApi {
|
||||
/// Paginates over the space tree in a depth-first manner to locate child rooms of a given space.
|
||||
///
|
||||
/// Where a child room is unknown to the local server, federation is used to fill in the details.
|
||||
/// The servers listed in the `via` array should be contacted to attempt to fill in missing rooms.
|
||||
///
|
||||
/// Only [`m.space.child`](#mspacechild) state events of the room are considered. Invalid child
|
||||
/// rooms and parent events are not covered by this endpoint.
|
||||
///
|
||||
/// [roomId] The room ID of the space to get a hierarchy for.
|
||||
///
|
||||
/// [suggestedOnly] Optional (default `false`) flag to indicate whether or not the server should only consider
|
||||
/// suggested rooms. Suggested rooms are annotated in their [`m.space.child`](#mspacechild) event
|
||||
/// contents.
|
||||
///
|
||||
/// [limit] Optional limit for the maximum number of rooms to include per response. Must be an integer
|
||||
/// greater than zero.
|
||||
///
|
||||
/// Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.
|
||||
///
|
||||
/// [maxDepth] Optional limit for how far to go into the space. Must be a non-negative integer.
|
||||
///
|
||||
/// When reached, no further child rooms will be returned.
|
||||
///
|
||||
/// Servers should apply a default value, and impose a maximum value to avoid resource exhaustion.
|
||||
///
|
||||
/// [from] A pagination token from a previous result. If specified, `max_depth` and `suggested_only` cannot
|
||||
/// be changed from the first request.
|
||||
Future<GetSpaceHierarchyResponse> getSpaceHierarchy(String roomId,
|
||||
{bool? suggestedOnly, int? limit, int? maxDepth, String? from}) async {
|
||||
final requestUri = Uri(
|
||||
path:
|
||||
'_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/hierarchy',
|
||||
queryParameters: {
|
||||
if (suggestedOnly != null) 'suggested_only': suggestedOnly.toString(),
|
||||
if (limit != null) 'limit': limit.toString(),
|
||||
if (maxDepth != null) 'max_depth': maxDepth.toString(),
|
||||
if (from != null) 'from': from,
|
||||
});
|
||||
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||
final response = await httpClient.send(request);
|
||||
final responseBody = await response.stream.toBytes();
|
||||
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||
final responseString = utf8.decode(responseBody);
|
||||
final json = jsonDecode(responseString);
|
||||
return GetSpaceHierarchyResponse.fromJson(json);
|
||||
}
|
||||
}
|
165
lib/msc/extension_spaces_advanced/src/space_hierarchy_model.dart
Normal file
165
lib/msc/extension_spaces_advanced/src/space_hierarchy_model.dart
Normal file
@ -0,0 +1,165 @@
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class SpaceRoomsChunkBase {
|
||||
SpaceRoomsChunkBase({
|
||||
required this.childrenState,
|
||||
required this.roomType,
|
||||
});
|
||||
|
||||
SpaceRoomsChunkBase.fromJson(Map<String, dynamic> json)
|
||||
: childrenState = (json['children_state'] as List)
|
||||
.map((v) => MatrixEvent.fromJson(v))
|
||||
.toList(),
|
||||
roomType = json['room_type'] as String;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'children_state': childrenState.map((v) => v.toJson()).toList(),
|
||||
'room_type': roomType,
|
||||
};
|
||||
|
||||
/// The [`m.space.child`](#mspacechild) events of the space-room, represented
|
||||
/// as [Stripped State Events](#stripped-state) with an added `origin_server_ts` key.
|
||||
///
|
||||
/// If the room is not a space-room, this should be empty.
|
||||
List<MatrixEvent> childrenState;
|
||||
|
||||
/// The `type` of room (from [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate)), if any.
|
||||
String? roomType;
|
||||
}
|
||||
|
||||
class SpaceRoomsChunk implements PublicRoomsChunk, SpaceRoomsChunkBase {
|
||||
SpaceRoomsChunk({
|
||||
this.avatarUrl,
|
||||
this.canonicalAlias,
|
||||
required this.guestCanJoin,
|
||||
this.joinRule,
|
||||
this.name,
|
||||
required this.numJoinedMembers,
|
||||
required this.roomId,
|
||||
this.topic,
|
||||
required this.worldReadable,
|
||||
required this.childrenState,
|
||||
required this.roomType,
|
||||
});
|
||||
|
||||
SpaceRoomsChunk.fromJson(Map<String, dynamic> json)
|
||||
: avatarUrl =
|
||||
((v) => v != null ? Uri.parse(v) : null)(json['avatar_url']),
|
||||
canonicalAlias =
|
||||
((v) => v != null ? v as String : null)(json['canonical_alias']),
|
||||
guestCanJoin = json['guest_can_join'] as bool,
|
||||
joinRule = ((v) => v != null ? v as String : null)(json['join_rule']),
|
||||
name = ((v) => v != null ? v as String : null)(json['name']),
|
||||
numJoinedMembers = json['num_joined_members'] as int,
|
||||
roomId = json['room_id'] as String,
|
||||
topic = ((v) => v != null ? v as String : null)(json['topic']),
|
||||
worldReadable = json['world_readable'] as bool,
|
||||
childrenState = (json['children_state'] as List)
|
||||
.map((v) => MatrixEvent.fromJson((v as Map<String, dynamic>)
|
||||
..putIfAbsent('event_id', () => 'invalid')))
|
||||
.toList(),
|
||||
roomType = json['room_type'] as String?;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final avatarUrl = this.avatarUrl;
|
||||
final canonicalAlias = this.canonicalAlias;
|
||||
final joinRule = this.joinRule;
|
||||
final name = this.name;
|
||||
final topic = this.topic;
|
||||
return {
|
||||
if (avatarUrl != null) 'avatar_url': avatarUrl.toString(),
|
||||
if (canonicalAlias != null) 'canonical_alias': canonicalAlias,
|
||||
'guest_can_join': guestCanJoin,
|
||||
if (joinRule != null) 'join_rule': joinRule,
|
||||
if (name != null) 'name': name,
|
||||
'num_joined_members': numJoinedMembers,
|
||||
'room_id': roomId,
|
||||
if (topic != null) 'topic': topic,
|
||||
'world_readable': worldReadable,
|
||||
'children_state': childrenState.map((v) => v.toJson()).toList(),
|
||||
'room_type': roomType,
|
||||
};
|
||||
}
|
||||
|
||||
/// The URL for the room's avatar, if one is set.
|
||||
@override
|
||||
Uri? avatarUrl;
|
||||
|
||||
/// The canonical alias of the room, if any.
|
||||
@override
|
||||
String? canonicalAlias;
|
||||
|
||||
/// Whether guest users may join the room and participate in it.
|
||||
/// If they can, they will be subject to ordinary power level
|
||||
/// rules like any other user.
|
||||
@override
|
||||
bool guestCanJoin;
|
||||
|
||||
/// The room's join rule. When not present, the room is assumed to
|
||||
/// be `public`.
|
||||
@override
|
||||
String? joinRule;
|
||||
|
||||
/// The name of the room, if any.
|
||||
@override
|
||||
String? name;
|
||||
|
||||
/// The number of members joined to the room.
|
||||
@override
|
||||
int numJoinedMembers;
|
||||
|
||||
/// The ID of the room.
|
||||
@override
|
||||
String roomId;
|
||||
|
||||
/// The topic of the room, if any.
|
||||
@override
|
||||
String? topic;
|
||||
|
||||
/// Whether the room may be viewed by guest users without joining.
|
||||
@override
|
||||
bool worldReadable;
|
||||
|
||||
/// The [`m.space.child`](#mspacechild) events of the space-room, represented
|
||||
/// as [Stripped State Events](#stripped-state) with an added `origin_server_ts` key.
|
||||
///
|
||||
/// If the room is not a space-room, this should be empty.
|
||||
@override
|
||||
List<MatrixEvent> childrenState;
|
||||
|
||||
/// The `type` of room (from [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate)), if any.
|
||||
@override
|
||||
String? roomType;
|
||||
|
||||
@override
|
||||
List<String>? aliases;
|
||||
}
|
||||
|
||||
class GetSpaceHierarchyResponse {
|
||||
GetSpaceHierarchyResponse({
|
||||
this.nextBatch,
|
||||
required this.rooms,
|
||||
});
|
||||
|
||||
GetSpaceHierarchyResponse.fromJson(Map<String, dynamic> json)
|
||||
: nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']),
|
||||
rooms = (json['rooms'] as List)
|
||||
.map((v) => SpaceRoomsChunk.fromJson(v))
|
||||
.toList();
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final nextBatch = this.nextBatch;
|
||||
return {
|
||||
if (nextBatch != null) 'next_batch': nextBatch,
|
||||
'rooms': rooms.map((v) => v.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// A token to supply to `from` to keep paginating the responses. Not present when there are
|
||||
/// no further results.
|
||||
String? nextBatch;
|
||||
|
||||
/// The rooms for the current page, with the current filters.
|
||||
List<SpaceRoomsChunk> rooms;
|
||||
}
|
@ -6,12 +6,16 @@ import 'package:animations/animations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/msc/extension_spaces_advanced/spaces_advaced.dart';
|
||||
import 'package:fluffychat/msc/extension_spaces_advanced/src/space_hierarchy_model.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
|
||||
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
|
||||
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
|
||||
import 'package:fluffychat/pages/chat_list/stories_header.dart';
|
||||
import '../../utils/stream_extension.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'recommended_room_list_item.dart';
|
||||
|
||||
class ChatListViewBody extends StatefulWidget {
|
||||
final ChatListController controller;
|
||||
@ -49,61 +53,137 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
|
||||
if (widget.controller.waitForFirstSync &&
|
||||
Matrix.of(context).client.prevBatch != null) {
|
||||
final rooms = widget.controller.activeSpacesEntry.getRooms(context);
|
||||
if (rooms.isEmpty) {
|
||||
child = Column(
|
||||
key: const ValueKey(null),
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/private_chat_wallpaper.png',
|
||||
width: 160,
|
||||
height: 160,
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
L10n.of(context)!.startYourFirstChat,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final displayStoriesHeader =
|
||||
widget.controller.activeSpacesEntry.shouldShowStoriesHeader(context);
|
||||
|
||||
final space = widget.controller.activeSpacesEntry.getSpace(context);
|
||||
|
||||
Future<GetSpaceHierarchyResponse?> hierarchyFuture;
|
||||
bool skipFuture;
|
||||
|
||||
// check for recommended rooms in case the active space is a [SpaceSpacesEntry]
|
||||
if (widget.controller.activeSpacesEntry is SpaceSpacesEntry &&
|
||||
space != null) {
|
||||
skipFuture = false;
|
||||
hierarchyFuture = Matrix.of(context)
|
||||
.client
|
||||
.getSpaceHierarchy(space.id, suggestedOnly: true, maxDepth: 1);
|
||||
} else {
|
||||
final displayStoriesHeader = widget.controller.activeSpacesEntry
|
||||
.shouldShowStoriesHeader(context);
|
||||
child = ListView.builder(
|
||||
skipFuture = true;
|
||||
hierarchyFuture = Future(() async => null);
|
||||
}
|
||||
|
||||
child = FutureBuilder<GetSpaceHierarchyResponse?>(
|
||||
key: ValueKey(Matrix.of(context).client.userID.toString() +
|
||||
widget.controller.activeSpaceId.toString() +
|
||||
widget.controller.activeSpacesEntry.runtimeType.toString()),
|
||||
controller: widget.controller.scrollController,
|
||||
// add +1 space below in order to properly scroll below the spaces bar
|
||||
itemCount: rooms.length + (displayStoriesHeader ? 2 : 1),
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
if (displayStoriesHeader) {
|
||||
if (i == 0) {
|
||||
return const StoriesHeader();
|
||||
}
|
||||
i--;
|
||||
}
|
||||
if (i >= rooms.length) {
|
||||
return const ListTile();
|
||||
}
|
||||
return ChatListItem(
|
||||
rooms[i],
|
||||
selected: widget.controller.selectedRoomIds.contains(rooms[i].id),
|
||||
onTap: widget.controller.selectMode == SelectMode.select
|
||||
? () => widget.controller.toggleSelection(rooms[i].id)
|
||||
: null,
|
||||
onLongPress: () => widget.controller.toggleSelection(rooms[i].id),
|
||||
activeChat: widget.controller.activeChat == rooms[i].id,
|
||||
future: hierarchyFuture,
|
||||
builder: (context, snapshot) {
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
int recommendedCount = snapshot.hasData
|
||||
? snapshot.data?.rooms
|
||||
.where((element) =>
|
||||
client.rooms.indexWhere(
|
||||
(room) => element.roomId == room.id) ==
|
||||
-1)
|
||||
.length ??
|
||||
0
|
||||
: 0;
|
||||
|
||||
// adding space for separator
|
||||
if (recommendedCount != 0) recommendedCount++;
|
||||
|
||||
int joinedCount = rooms.length + (displayStoriesHeader ? 1 : 0);
|
||||
if (rooms.isEmpty) joinedCount++;
|
||||
|
||||
final count = joinedCount + recommendedCount + 1;
|
||||
|
||||
return ListView.builder(
|
||||
controller: widget.controller.scrollController,
|
||||
// add +1 space below in order to properly scroll below the spaces bar
|
||||
itemCount: count,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
// first render the stories header
|
||||
if (displayStoriesHeader && i == 0) {
|
||||
return const StoriesHeader();
|
||||
}
|
||||
// the room tiles afterwards
|
||||
else if (i < joinedCount) {
|
||||
// in case there are no joined rooms, display friendly graphics
|
||||
if (rooms.isEmpty) {
|
||||
return Column(
|
||||
key: const ValueKey(null),
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/private_chat_wallpaper.png',
|
||||
width: 160,
|
||||
height: 160,
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
L10n.of(context)!.startYourFirstChat,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
final room = rooms[i - (displayStoriesHeader ? 1 : 0)];
|
||||
return ChatListItem(
|
||||
room,
|
||||
selected:
|
||||
widget.controller.selectedRoomIds.contains(room.id),
|
||||
onTap: widget.controller.selectMode == SelectMode.select
|
||||
? () => widget.controller.toggleSelection(room.id)
|
||||
: null,
|
||||
onLongPress: () =>
|
||||
widget.controller.toggleSelection(room.id),
|
||||
activeChat: widget.controller.activeChat == room.id,
|
||||
);
|
||||
// display a trailing empty list tile at last position to avoid overflow with bottom bar
|
||||
} else if (i == count - 1) {
|
||||
return skipFuture || snapshot.hasData
|
||||
? const ListTile()
|
||||
: Column(
|
||||
children: const [
|
||||
Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
ListTile(),
|
||||
],
|
||||
);
|
||||
// at the last position before the recommendations, show separator
|
||||
} else if (i == joinedCount - (displayStoriesHeader ? 1 : 0)) {
|
||||
return Column(
|
||||
children: [
|
||||
const ListTile(),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.explore),
|
||||
title: Text(L10n.of(context)!.discoverGroups))
|
||||
],
|
||||
);
|
||||
// only recommendation tiles left
|
||||
} else {
|
||||
final roomPreview = snapshot.data!.rooms
|
||||
.where((element) =>
|
||||
client.rooms.indexWhere(
|
||||
(room) => element.roomId == room.id) ==
|
||||
-1)
|
||||
.toList()[i - joinedCount - 1];
|
||||
return RecommendedRoomListItem(room: roomPreview);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const dummyChatCount = 5;
|
||||
final titleColor =
|
||||
|
170
lib/pages/chat_list/recommended_room_list_item.dart
Normal file
170
lib/pages/chat_list/recommended_room_list_item.dart
Normal file
@ -0,0 +1,170 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/msc/extension_spaces_advanced/src/space_hierarchy_model.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/content_banner.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class RecommendedRoomListItem extends StatelessWidget {
|
||||
final SpaceRoomsChunk room;
|
||||
|
||||
const RecommendedRoomListItem({Key? key, required this.room})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: ListTile(
|
||||
leading: Avatar(
|
||||
mxContent: room.avatarUrl,
|
||||
name: room.name,
|
||||
),
|
||||
title: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
room.name ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).textTheme.bodyText1!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
// number of joined users
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text.rich(
|
||||
TextSpan(children: [
|
||||
WidgetSpan(
|
||||
child: Tooltip(
|
||||
child: const Icon(Icons.people),
|
||||
message: L10n.of(context)!
|
||||
.numberRoomMembers(room.numJoinedMembers),
|
||||
),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
baseline: TextBaseline.alphabetic),
|
||||
TextSpan(text: ' ${room.numJoinedMembers}')
|
||||
]),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).textTheme.bodyText2!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
room.topic ?? 'topic',
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyText2!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (c) => RecommendedRoomPreview(room: room)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecommendedRoomPreview extends StatelessWidget {
|
||||
final SpaceRoomsChunk room;
|
||||
|
||||
const RecommendedRoomPreview({Key? key, required this.room})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: min(
|
||||
MediaQuery.of(context).size.width, FluffyThemes.columnWidth * 1.5),
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_downward_outlined),
|
||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
),
|
||||
title: Text(room.name ?? L10n.of(context)!.chatDetails),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ContentBanner(
|
||||
mxContent: room.avatarUrl,
|
||||
defaultIcon: Icons.group,
|
||||
client: client,
|
||||
),
|
||||
),
|
||||
if (room.topic != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.groupDescription),
|
||||
subtitle: Text(room.topic!),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.link),
|
||||
subtitle: Text(room.roomId),
|
||||
trailing: Icon(Icons.adaptive.share_outlined),
|
||||
onTap: () => FluffyShare.share(room.roomId, context),
|
||||
),
|
||||
ListTile(
|
||||
title: ElevatedButton(
|
||||
onPressed: () async {
|
||||
final client = Matrix.of(context).client;
|
||||
final joinedFuture = client.onSync.stream
|
||||
.where((u) =>
|
||||
u.rooms?.join?.containsKey(room.roomId) ??
|
||||
false)
|
||||
.first;
|
||||
final router = VRouter.of(context);
|
||||
await client.joinRoomById(room.roomId);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (c) =>
|
||||
LoadingDialog(future: () => joinedFuture));
|
||||
Navigator.of(context).pop();
|
||||
router.toSegments(['rooms', room.roomId]);
|
||||
},
|
||||
child: Text(L10n.of(context)!.joinRoom)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user