feat: Implement room tags
This commit is contained in:
parent
b38d111264
commit
101578bc9d
3 changed files with 165 additions and 38 deletions
|
|
@ -2798,5 +2798,9 @@
|
|||
"joinVoiceCall": "Join voice call",
|
||||
"joinVideoCall": "Join video call",
|
||||
"live": "Live",
|
||||
"playSoundOnNotification": "Play sound on notification"
|
||||
"playSoundOnNotification": "Play sound on notification",
|
||||
"addTag": "Add tag",
|
||||
"removeTag": "Remove tag",
|
||||
"tagName": "Tag name",
|
||||
"createNewTag": "Create new tag"
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ import '../../config/setting_keys.dart';
|
|||
import '../../utils/url_launcher.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
|
||||
enum ActiveFilter { allChats, messages, groups, unread, spaces }
|
||||
enum ActiveFilter { allChats, messages, groups, unread, spaces, tag }
|
||||
|
||||
extension LocalizedActiveFilter on ActiveFilter {
|
||||
String toLocalizedString(BuildContext context) {
|
||||
|
|
@ -45,6 +45,8 @@ extension LocalizedActiveFilter on ActiveFilter {
|
|||
return L10n.of(context).groups;
|
||||
case ActiveFilter.spaces:
|
||||
return L10n.of(context).spaces;
|
||||
case ActiveFilter.tag:
|
||||
throw 'Tags should not directly be displayed!';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +75,7 @@ class ChatListController extends State<ChatList>
|
|||
StreamSubscription? _intentFileStreamSubscription;
|
||||
|
||||
late ActiveFilter activeFilter;
|
||||
String? activeTag;
|
||||
|
||||
String? _activeSpaceId;
|
||||
String? get activeSpaceId => _activeSpaceId;
|
||||
|
|
@ -141,6 +144,8 @@ class ChatListController extends State<ChatList>
|
|||
return (room) => room.isUnreadOrInvited;
|
||||
case ActiveFilter.spaces:
|
||||
return (room) => room.isSpace;
|
||||
case ActiveFilter.tag:
|
||||
return (room) => room.tags.keys.contains(activeTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -358,13 +363,10 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
}
|
||||
|
||||
StreamSubscription? _onRoomTagUpdate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
activeFilter =
|
||||
ActiveFilter.values.singleWhereOrNull(
|
||||
(filter) => AppSettings.chatFilter.value == filter.name,
|
||||
) ??
|
||||
ActiveFilter.allChats;
|
||||
_initReceiveSharingIntent();
|
||||
_activeSpaceId = widget.activeSpace;
|
||||
|
||||
|
|
@ -387,6 +389,32 @@ class ChatListController extends State<ChatList>
|
|||
);
|
||||
});
|
||||
|
||||
_updateRoomTags();
|
||||
_onRoomTagUpdate = Matrix.of(context).client.onSync.stream
|
||||
.where(
|
||||
(syncUpdate) =>
|
||||
syncUpdate.rooms?.join?.values.any(
|
||||
(roomUpdate) =>
|
||||
roomUpdate.accountData?.any(
|
||||
(accountData) => accountData.type == 'm.tag',
|
||||
) ??
|
||||
false,
|
||||
) ??
|
||||
false,
|
||||
)
|
||||
.listen(_updateRoomTags);
|
||||
|
||||
if (roomTags.containsKey(AppSettings.chatFilter.value)) {
|
||||
activeFilter = ActiveFilter.tag;
|
||||
activeTag = AppSettings.chatFilter.value;
|
||||
} else {
|
||||
activeFilter =
|
||||
ActiveFilter.values.singleWhereOrNull(
|
||||
(filter) => AppSettings.chatFilter.value == filter.name,
|
||||
) ??
|
||||
ActiveFilter.allChats;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -394,6 +422,7 @@ class ChatListController extends State<ChatList>
|
|||
void dispose() {
|
||||
_intentDataStreamSubscription?.cancel();
|
||||
_intentFileStreamSubscription?.cancel();
|
||||
_onRoomTagUpdate?.cancel();
|
||||
scrollController.removeListener(_onScroll);
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -622,6 +651,30 @@ class ChatListController extends State<ChatList>
|
|||
],
|
||||
),
|
||||
),
|
||||
if (activeTag == null)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.addTag,
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(Icons.bookmark_add_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).addTag),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.removeTag,
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(Icons.bookmark_remove_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).removeTag),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (spacesWithPowerLevels.isNotEmpty)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.addToSpace,
|
||||
|
|
@ -762,9 +815,68 @@ class ChatListController extends State<ChatList>
|
|||
future: () => room.setLowPriority(!room.isLowPriority),
|
||||
);
|
||||
return;
|
||||
case ChatContextAction.addTag:
|
||||
final existingTags = List.of(roomTags.keys);
|
||||
existingTags.removeWhere(room.tags.containsKey);
|
||||
String? tag;
|
||||
if (existingTags.isNotEmpty) {
|
||||
tag = await showModalActionPopup<String?>(
|
||||
context: context,
|
||||
actions: [
|
||||
...existingTags.map((tag) {
|
||||
final displayTag = tag.replaceFirst('u.', '');
|
||||
return AdaptiveModalAction(
|
||||
label: displayTag,
|
||||
value: displayTag,
|
||||
);
|
||||
}),
|
||||
AdaptiveModalAction(
|
||||
label: L10n.of(context).createNewTag,
|
||||
value: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (!mounted) return;
|
||||
}
|
||||
tag ??= await showTextInputDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).addTag,
|
||||
hintText: L10n.of(context).tagName,
|
||||
);
|
||||
final newTag = tag;
|
||||
if (!mounted) return;
|
||||
if (newTag == null) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.addTag('u.$newTag'),
|
||||
);
|
||||
return;
|
||||
case ChatContextAction.removeTag:
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.removeTag(activeTag!),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, int> roomTags = {};
|
||||
|
||||
void _updateRoomTags([_]) {
|
||||
roomTags.clear();
|
||||
for (final room in Matrix.of(context).client.rooms) {
|
||||
for (final tag in room.tags.keys) {
|
||||
if (tag.startsWith('u.')) roomTags[tag] = (roomTags[tag] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
if (activeTag != null && !roomTags.keys.contains(activeTag)) {
|
||||
activeTag = null;
|
||||
activeFilter = ActiveFilter.allChats;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dismissStatusList() async {
|
||||
final result = await showOkCancelAlertDialog(
|
||||
title: L10n.of(context).hidePresences,
|
||||
|
|
@ -858,11 +970,17 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
}
|
||||
|
||||
void setActiveFilter(ActiveFilter filter) {
|
||||
void setActiveFilter(ActiveFilter filter, String? tag) {
|
||||
if (filter == ActiveFilter.tag && tag == null) {
|
||||
throw ('Must set a tag when setting filter to tags!');
|
||||
}
|
||||
setState(() {
|
||||
activeTag = tag;
|
||||
activeFilter = filter;
|
||||
});
|
||||
AppSettings.chatFilter.setItem(filter.name);
|
||||
AppSettings.chatFilter.setItem(
|
||||
filter == ActiveFilter.tag ? tag! : filter.name,
|
||||
);
|
||||
}
|
||||
|
||||
void setActiveClient(Client client) {
|
||||
|
|
@ -978,6 +1096,8 @@ enum ChatContextAction {
|
|||
goToSpace,
|
||||
favorite,
|
||||
lowPriority,
|
||||
addTag,
|
||||
removeTag,
|
||||
markUnread,
|
||||
mute,
|
||||
leave,
|
||||
|
|
|
|||
|
|
@ -134,37 +134,40 @@ class ChatListViewBody extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(12.0),
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children:
|
||||
[
|
||||
ActiveFilter.allChats,
|
||||
|
||||
if (spaces.isNotEmpty &&
|
||||
!AppSettings
|
||||
.displayNavigationRail
|
||||
.value &&
|
||||
!FluffyThemes.isColumnMode(context))
|
||||
ActiveFilter.spaces,
|
||||
ActiveFilter.unread,
|
||||
ActiveFilter.groups,
|
||||
ActiveFilter.messages,
|
||||
]
|
||||
.map(
|
||||
(filter) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: FilterChip(
|
||||
selected:
|
||||
filter == controller.activeFilter,
|
||||
onSelected: (_) =>
|
||||
controller.setActiveFilter(filter),
|
||||
label: Text(
|
||||
filter.toLocalizedString(context),
|
||||
),
|
||||
children: [
|
||||
...ActiveFilter.values
|
||||
.where((filter) => filter != ActiveFilter.tag)
|
||||
.map(
|
||||
(filter) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: FilterChip(
|
||||
selected: filter == controller.activeFilter,
|
||||
onSelected: (_) => controller
|
||||
.setActiveFilter(filter, null),
|
||||
label: Text(
|
||||
filter.toLocalizedString(context),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
...controller.roomTags.entries.map(
|
||||
(entry) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: FilterChip(
|
||||
selected: entry.key == controller.activeTag,
|
||||
onSelected: (_) => controller.setActiveFilter(
|
||||
ActiveFilter.tag,
|
||||
entry.key,
|
||||
),
|
||||
label: Text(entry.key.replaceFirst('u.', '')),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (controller.isSearchMode)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue