mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-23 20:49:26 +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
@ -2769,6 +2769,13 @@
|
|||||||
"number": {}
|
"number": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"numberRoomMembers": "{number} members",
|
||||||
|
"@numberRoomMembers": {
|
||||||
|
"type": "number",
|
||||||
|
"placeholders": {
|
||||||
|
"number": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nextAccount": "Next account",
|
"nextAccount": "Next account",
|
||||||
"previousAccount": "Previous account",
|
"previousAccount": "Previous account",
|
||||||
"editWidgets": "Edit widgets",
|
"editWidgets": "Edit widgets",
|
||||||
|
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:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:matrix/matrix.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.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list_item.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_bottom_bar.dart';
|
||||||
|
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/stories_header.dart';
|
import 'package:fluffychat/pages/chat_list/stories_header.dart';
|
||||||
import '../../utils/stream_extension.dart';
|
import '../../utils/stream_extension.dart';
|
||||||
import '../../widgets/matrix.dart';
|
import '../../widgets/matrix.dart';
|
||||||
|
import 'recommended_room_list_item.dart';
|
||||||
|
|
||||||
class ChatListViewBody extends StatefulWidget {
|
class ChatListViewBody extends StatefulWidget {
|
||||||
final ChatListController controller;
|
final ChatListController controller;
|
||||||
@ -49,8 +53,67 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
|
|||||||
if (widget.controller.waitForFirstSync &&
|
if (widget.controller.waitForFirstSync &&
|
||||||
Matrix.of(context).client.prevBatch != null) {
|
Matrix.of(context).client.prevBatch != null) {
|
||||||
final rooms = widget.controller.activeSpacesEntry.getRooms(context);
|
final rooms = widget.controller.activeSpacesEntry.getRooms(context);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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()),
|
||||||
|
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) {
|
if (rooms.isEmpty) {
|
||||||
child = Column(
|
return Column(
|
||||||
key: const ValueKey(null),
|
key: const ValueKey(null),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -72,38 +135,55 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
final displayStoriesHeader = widget.controller.activeSpacesEntry
|
|
||||||
.shouldShowStoriesHeader(context);
|
|
||||||
child = ListView.builder(
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
final room = rooms[i - (displayStoriesHeader ? 1 : 0)];
|
||||||
return ChatListItem(
|
return ChatListItem(
|
||||||
rooms[i],
|
room,
|
||||||
selected: widget.controller.selectedRoomIds.contains(rooms[i].id),
|
selected:
|
||||||
|
widget.controller.selectedRoomIds.contains(room.id),
|
||||||
onTap: widget.controller.selectMode == SelectMode.select
|
onTap: widget.controller.selectMode == SelectMode.select
|
||||||
? () => widget.controller.toggleSelection(rooms[i].id)
|
? () => widget.controller.toggleSelection(room.id)
|
||||||
: null,
|
: null,
|
||||||
onLongPress: () => widget.controller.toggleSelection(rooms[i].id),
|
onLongPress: () =>
|
||||||
activeChat: widget.controller.activeChat == rooms[i].id,
|
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 {
|
} else {
|
||||||
const dummyChatCount = 5;
|
const dummyChatCount = 5;
|
||||||
final titleColor =
|
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