chore: Follow up spaces ui

pull/1223/head
Krille 1 year ago
parent 3bd7257249
commit 650f87b1d2
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652

@ -2710,5 +2710,6 @@
"@goToSpace": { "@goToSpace": {
"type": "text", "type": "text",
"space": {} "space": {}
} },
"markAsUnread": "Mark as unread"
} }

@ -619,7 +619,7 @@ class ChatListController extends State<ChatList>
if (space != null) if (space != null)
SheetAction( SheetAction(
key: ChatContextAction.goToSpace, key: ChatContextAction.goToSpace,
icon: Icons.workspaces_outlined, icon: Icons.chevron_right_outlined,
label: L10n.of(context)!.goToSpace(space.getLocalizedDisplayname()), label: L10n.of(context)!.goToSpace(space.getLocalizedDisplayname()),
), ),
SheetAction( SheetAction(
@ -627,11 +627,13 @@ class ChatListController extends State<ChatList>
icon: room.markedUnread icon: room.markedUnread
? Icons.mark_as_unread ? Icons.mark_as_unread
: Icons.mark_as_unread_outlined, : Icons.mark_as_unread_outlined,
label: L10n.of(context)!.toggleUnread, label: room.markedUnread
? L10n.of(context)!.markAsRead
: L10n.of(context)!.markAsUnread,
), ),
SheetAction( SheetAction(
key: ChatContextAction.favorite, key: ChatContextAction.favorite,
icon: room.isFavourite ? Icons.pin : Icons.pin_outlined, icon: room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined,
label: room.isFavourite label: room.isFavourite
? L10n.of(context)!.unpin ? L10n.of(context)!.unpin
: L10n.of(context)!.pin, : L10n.of(context)!.pin,
@ -640,7 +642,7 @@ class ChatListController extends State<ChatList>
key: ChatContextAction.mute, key: ChatContextAction.mute,
icon: room.pushRuleState == PushRuleState.notify icon: room.pushRuleState == PushRuleState.notify
? Icons.notifications_off_outlined ? Icons.notifications_off_outlined
: Icons.notifications, : Icons.notifications_outlined,
label: room.pushRuleState == PushRuleState.notify label: room.pushRuleState == PushRuleState.notify
? L10n.of(context)!.muteChat ? L10n.of(context)!.muteChat
: L10n.of(context)!.unmuteChat, : L10n.of(context)!.unmuteChat,
@ -657,6 +659,19 @@ class ChatListController extends State<ChatList>
if (action == null) return; if (action == null) return;
if (!mounted) return; if (!mounted) return;
if (action == ChatContextAction.leave) {
final confirmed = await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.no,
message: L10n.of(context)!.archiveRoomDescription,
);
if (confirmed == OkCancelResult.cancel) return;
}
if (!mounted) return;
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
future: () async { future: () async {

@ -22,14 +22,9 @@ class ChatListView extends StatelessWidget {
final selectMode = controller.selectMode; final selectMode = controller.selectMode;
return PopScope( return PopScope(
canPop: controller.selectMode == SelectMode.normal && canPop: controller.selectMode == SelectMode.normal &&
!controller.isSearchMode && !controller.isSearchMode,
controller.activeFilter == ActiveFilter.allChats, onPopInvoked: (pop) {
onPopInvoked: (pop) async {
if (pop) return; if (pop) return;
if (controller.activeSpaceId != null) {
controller.clearActiveSpace();
return;
}
final selMode = controller.selectMode; final selMode = controller.selectMode;
if (controller.isSearchMode) { if (controller.isSearchMode) {
controller.cancelSearch(); controller.cancelSearch();

@ -165,316 +165,312 @@ class _SpaceViewState extends State<SpaceView> {
final room = Matrix.of(context).client.getRoomById(widget.spaceId); final room = Matrix.of(context).client.getRoomById(widget.spaceId);
final displayname = final displayname =
room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound; room?.getLocalizedDisplayname() ?? L10n.of(context)!.nothingFound;
return Scaffold( return PopScope(
appBar: AppBar( canPop: false,
leading: Center( onPopInvoked: (_) => widget.onBack(),
child: CloseButton( child: Scaffold(
onPressed: widget.onBack, appBar: AppBar(
), leading: Center(
), child: CloseButton(
titleSpacing: 0, onPressed: widget.onBack,
title: ListTile( ),
contentPadding: EdgeInsets.zero,
leading: Avatar(
mxContent: room?.avatar,
name: displayname,
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
), ),
title: Text( titleSpacing: 0,
displayname, title: ListTile(
maxLines: 1, contentPadding: EdgeInsets.zero,
overflow: TextOverflow.ellipsis, leading: Avatar(
mxContent: room?.avatar,
name: displayname,
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
),
title: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: room == null
? null
: Text(
L10n.of(context)!.countChatsAndCountParticipants(
room.spaceChildren.length,
room.summary.mJoinedMemberCount ?? 1,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
subtitle: room == null actions: [
? null PopupMenuButton<SpaceActions>(
: Text( onSelected: _onSpaceAction,
L10n.of(context)!.countChatsAndCountParticipants( itemBuilder: (context) => [
room.spaceChildren.length, PopupMenuItem(
room.summary.mJoinedMemberCount ?? 1, value: SpaceActions.settings,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.settings_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.settings),
],
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
), PopupMenuItem(
actions: [ value: SpaceActions.invite,
PopupMenuButton<SpaceActions>( child: Row(
onSelected: _onSpaceAction, mainAxisSize: MainAxisSize.min,
itemBuilder: (context) => [ children: [
PopupMenuItem( const Icon(Icons.person_add_outlined),
value: SpaceActions.settings, const SizedBox(width: 12),
child: Row( Text(L10n.of(context)!.invite),
mainAxisSize: MainAxisSize.min, ],
children: [ ),
const Icon(Icons.settings_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.settings),
],
), ),
), PopupMenuItem(
PopupMenuItem( value: SpaceActions.leave,
value: SpaceActions.invite, child: Row(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ const Icon(Icons.delete_outlined),
const Icon(Icons.person_add_outlined), const SizedBox(width: 12),
const SizedBox(width: 12), Text(L10n.of(context)!.leave),
Text(L10n.of(context)!.invite), ],
], ),
), ),
), ],
PopupMenuItem( ),
value: SpaceActions.leave, ],
child: Row( ),
mainAxisSize: MainAxisSize.min, body: room == null
children: [ ? const Center(
const Icon(Icons.delete_outlined), child: Icon(
const SizedBox(width: 12), Icons.search_outlined,
Text(L10n.of(context)!.leave), size: 80,
],
), ),
), )
], : StreamBuilder(
), stream: room.client.onSync.stream
], .where((s) => s.hasRoomUpdate)
), .rateLimit(const Duration(seconds: 1)),
body: room == null builder: (context, snapshot) {
? const Center( final childrenIds = room.spaceChildren
child: Icon( .map((c) => c.roomId)
Icons.search_outlined, .whereType<String>()
size: 80, .toSet();
),
)
: StreamBuilder(
stream: room.client.onSync.stream
.where((s) => s.hasRoomUpdate)
.rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) {
final joinedRooms = room.spaceChildren
.map((child) {
final roomId = child.roomId;
if (roomId == null) return null;
return room.client.getRoomById(roomId);
})
.whereType<Room>()
.where((room) => room.membership != Membership.leave)
.toList();
// Sort rooms by last activity final joinedRooms = room.client.rooms
joinedRooms.sort( .where((room) => childrenIds.remove(room.id))
(b, a) => (a.lastEvent?.originServerTs ?? .toList();
DateTime.fromMillisecondsSinceEpoch(0))
.compareTo(
b.lastEvent?.originServerTs ??
DateTime.fromMillisecondsSinceEpoch(0),
),
);
final joinedParents = room.spaceParents final joinedParents = room.spaceParents
.map((parent) { .map((parent) {
final roomId = parent.roomId; final roomId = parent.roomId;
if (roomId == null) return null; if (roomId == null) return null;
return room.client.getRoomById(roomId); return room.client.getRoomById(roomId);
}) })
.whereType<Room>() .whereType<Room>()
.toList(); .toList();
final filter = _filterController.text.trim().toLowerCase(); final filter = _filterController.text.trim().toLowerCase();
return CustomScrollView( return CustomScrollView(
slivers: [ slivers: [
SliverAppBar( SliverAppBar(
floating: true, floating: true,
toolbarHeight: 72, toolbarHeight: 72,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: TextField( title: TextField(
controller: _filterController, controller: _filterController,
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
decoration: InputDecoration( decoration: InputDecoration(
fillColor: fillColor: Theme.of(context)
Theme.of(context).colorScheme.secondaryContainer,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(99),
),
contentPadding: EdgeInsets.zero,
hintText: L10n.of(context)!.search,
hintStyle: TextStyle(
color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer, .secondaryContainer,
fontWeight: FontWeight.normal, border: OutlineInputBorder(
), borderSide: BorderSide.none,
floatingLabelBehavior: FloatingLabelBehavior.never, borderRadius: BorderRadius.circular(99),
prefixIcon: IconButton( ),
onPressed: () {}, contentPadding: EdgeInsets.zero,
icon: Icon( hintText: L10n.of(context)!.search,
Icons.search_outlined, hintStyle: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer, .onPrimaryContainer,
fontWeight: FontWeight.normal,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
prefixIcon: IconButton(
onPressed: () {},
icon: Icon(
Icons.search_outlined,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
), ),
), ),
), ),
), ),
), SliverList.builder(
SliverList.builder( itemCount: joinedParents.length,
itemCount: joinedParents.length, itemBuilder: (context, i) {
itemBuilder: (context, i) { final displayname =
final displayname = joinedParents[i].getLocalizedDisplayname();
joinedParents[i].getLocalizedDisplayname(); return Padding(
return Padding( padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( horizontal: 8,
horizontal: 8, vertical: 1,
vertical: 1, ),
), child: Material(
child: Material( borderRadius:
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
BorderRadius.circular(AppConfig.borderRadius), clipBehavior: Clip.hardEdge,
clipBehavior: Clip.hardEdge, child: ListTile(
child: ListTile( minVerticalPadding: 0,
minVerticalPadding: 0, leading: Icon(
leading: Icon( Icons.adaptive.arrow_back_outlined,
Icons.adaptive.arrow_back_outlined, size: 16,
size: 16, ),
), title: Row(
title: Row( children: [
children: [ Avatar(
Avatar( mxContent: joinedParents[i].avatar,
mxContent: joinedParents[i].avatar, name: displayname,
name: displayname, size: Avatar.defaultSize / 2,
size: Avatar.defaultSize / 2, borderRadius: BorderRadius.circular(
borderRadius: BorderRadius.circular( AppConfig.borderRadius / 4,
AppConfig.borderRadius / 4, ),
), ),
), const SizedBox(width: 8),
const SizedBox(width: 8), Expanded(child: Text(displayname)),
Expanded(child: Text(displayname)), ],
], ),
onTap: () =>
widget.toParentSpace(joinedParents[i].id),
), ),
onTap: () =>
widget.toParentSpace(joinedParents[i].id),
), ),
),
);
},
),
SliverList.builder(
itemCount: joinedRooms.length + 1,
itemBuilder: (context, i) {
if (i == 0) {
return SearchTitle(
title: L10n.of(context)!.joinedChats,
icon: const Icon(Icons.chat_outlined),
); );
} },
i--; ),
final room = joinedRooms[i]; SliverList.builder(
return ChatListItem( itemCount: joinedRooms.length + 1,
room, itemBuilder: (context, i) {
filter: filter, if (i == 0) {
onTap: () => widget.onChatTab(room), return SearchTitle(
onLongPress: () => widget.onChatContext(room), title: L10n.of(context)!.joinedChats,
activeChat: widget.activeChat == room.id, icon: const Icon(Icons.chat_outlined),
); );
}, }
), i--;
SliverList.builder( final room = joinedRooms[i];
itemCount: _discoveredChildren.length + 2, return ChatListItem(
itemBuilder: (context, i) { room,
if (i == 0) { filter: filter,
return SearchTitle( onTap: () => widget.onChatTab(room),
title: L10n.of(context)!.discover, onLongPress: () => widget.onChatContext(room),
icon: const Icon(Icons.explore_outlined), activeChat: widget.activeChat == room.id,
); );
} },
i--; ),
if (i == _discoveredChildren.length) { SliverList.builder(
if (_noMoreRooms) { itemCount: _discoveredChildren.length + 2,
return Padding( itemBuilder: (context, i) {
padding: const EdgeInsets.all(12.0), if (i == 0) {
child: Center( return SearchTitle(
child: Text( title: L10n.of(context)!.discover,
L10n.of(context)!.noMoreChatsFound, icon: const Icon(Icons.explore_outlined),
style: const TextStyle(fontSize: 13), );
}
i--;
if (i == _discoveredChildren.length) {
if (_noMoreRooms) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Center(
child: Text(
L10n.of(context)!.noMoreChatsFound,
style: const TextStyle(fontSize: 13),
),
), ),
);
}
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 2.0,
),
child: TextButton(
onPressed: _isLoading ? null : _loadHierarchy,
child: _isLoading
? LinearProgressIndicator(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
)
: Text(L10n.of(context)!.loadMore),
), ),
); );
} }
final item = _discoveredChildren[i];
final displayname = item.name ??
item.canonicalAlias ??
L10n.of(context)!.emptyChat;
if (!displayname.toLowerCase().contains(filter)) {
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 12.0, horizontal: 8,
vertical: 2.0, vertical: 1,
), ),
child: TextButton( child: Material(
onPressed: _isLoading ? null : _loadHierarchy, borderRadius:
child: _isLoading BorderRadius.circular(AppConfig.borderRadius),
? LinearProgressIndicator( clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular( child: ListTile(
AppConfig.borderRadius, onTap: () => _joinChildRoom(item),
leading: Avatar(
mxContent: item.avatarUrl,
name: displayname,
borderRadius: item.roomType == 'm.space'
? BorderRadius.circular(
AppConfig.borderRadius / 2,
)
: null,
),
title: Row(
children: [
Expanded(
child: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
)
: Text(L10n.of(context)!.loadMore),
),
);
}
final item = _discoveredChildren[i];
final displayname = item.name ??
item.canonicalAlias ??
L10n.of(context)!.emptyChat;
if (!displayname.toLowerCase().contains(filter)) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 1,
),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
clipBehavior: Clip.hardEdge,
child: ListTile(
onTap: () => _joinChildRoom(item),
leading: Avatar(
mxContent: item.avatarUrl,
name: displayname,
borderRadius: item.roomType == 'm.space'
? BorderRadius.circular(
AppConfig.borderRadius / 2,
)
: null,
),
title: Row(
children: [
Expanded(
child: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
), const SizedBox(width: 8),
const SizedBox(width: 8), const Icon(
const Icon(Icons.add_circle_outline_outlined), Icons.add_circle_outline_outlined,
],
),
subtitle: Text(
item.topic ??
L10n.of(context)!.countParticipants(
item.numJoinedMembers,
), ),
maxLines: 1, ],
overflow: TextOverflow.ellipsis, ),
subtitle: Text(
item.topic ??
L10n.of(context)!.countParticipants(
item.numJoinedMembers,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
), ),
), );
); },
}, ),
), ],
], );
); },
}, ),
), ),
); );
} }
} }

Loading…
Cancel
Save