diff --git a/lib/pages/chat_list.dart b/lib/pages/chat_list.dart index 6260205a..b816dae9 100644 --- a/lib/pages/chat_list.dart +++ b/lib/pages/chat_list.dart @@ -19,6 +19,7 @@ import 'package:uni_links/uni_links.dart'; import 'package:vrouter/vrouter.dart'; import '../main.dart'; import '../widgets/matrix.dart'; +import '../../utils/account_bundles.dart'; import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart'; import '../utils/url_launcher.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -416,6 +417,61 @@ class ChatListController extends State { i - (Matrix.of(context).hasComplexBundles ? 1 : 0); }); + void setActiveBundle(String bundle) => setState(() { + _activeSpaceId = null; + selectedRoomIds.clear(); + Matrix.of(context).activeBundle = bundle; + }); + + void editBundlesForAccount(String userId) async { + final client = Matrix.of(context) + .widget + .clients[Matrix.of(context).getClientIndexByMatrixId(userId)]; + final action = await showConfirmationDialog( + context: context, + title: 'Edit bundles for this account', + actions: [ + AlertDialogAction( + key: EditBundleAction.addToBundle, + label: 'Add to bundle', + ), + if (Matrix.of(context).activeBundle != null) + AlertDialogAction( + key: EditBundleAction.removeFromBundle, + label: 'Remove from this bundle', + ), + ], + ); + if (action == null) return; + switch (action) { + case EditBundleAction.addToBundle: + final bundle = await showTextInputDialog( + context: context, + title: 'Bundle name', + textFields: [DialogTextField(hintText: 'Bundle name')]); + if (bundle.isEmpty && bundle.single.isEmpty) return; + await showFutureLoadingDialog( + context: context, + future: () => client.setAccountBundle(bundle.single), + ); + break; + case EditBundleAction.removeFromBundle: + await showFutureLoadingDialog( + context: context, + future: () => + client.removeFromAccountBundle(Matrix.of(context).activeBundle), + ); + } + } + + void resetActiveBundle() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() { + Matrix.of(context).activeBundle = null; + }); + }); + } + @override Widget build(BuildContext context) { Matrix.of(context).navigatorContext = context; @@ -431,3 +487,5 @@ class ChatListController extends State { return ChatListView(this); } } + +enum EditBundleAction { addToBundle, removeFromBundle } diff --git a/lib/pages/views/chat_list_view.dart b/lib/pages/views/chat_list_view.dart index 28887159..eb4b88c7 100644 --- a/lib/pages/views/chat_list_view.dart +++ b/lib/pages/views/chat_list_view.dart @@ -1,6 +1,8 @@ import 'dart:math'; +import 'package:async/async.dart'; import 'package:fluffychat/widgets/avatar.dart'; +import 'package:flutter/widgets.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat_list.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; @@ -23,18 +25,30 @@ class ChatListView extends StatelessWidget { List getBottomBarItems(BuildContext context) { final displayClients = Matrix.of(context).hasComplexBundles ? Matrix.of(context).accountBundles[Matrix.of(context).activeBundle ?? - Matrix.of(context).client.accountBundles.first.name] + Matrix.of(context).client.accountBundles.first.name] ?? + [] : Matrix.of(context).widget.clients; + if (displayClients.isEmpty) { + displayClients.addAll(Matrix.of(context).widget.clients); + controller.resetActiveBundle(); + } final items = displayClients.map((client) { return BottomNavigationBarItem( label: client.userID, icon: FutureBuilder( future: client.ownProfile, builder: (context, snapshot) { - return Avatar( - snapshot.data?.avatarUrl, - snapshot.data?.displayName ?? client.userID.localpart, - size: 32, + return InkWell( + borderRadius: BorderRadius.circular(32), + onTap: () => controller.setActiveClient( + Matrix.of(context).getClientIndexByMatrixId(client.userID)), + onLongPress: () => + controller.editBundlesForAccount(client.userID), + child: Avatar( + snapshot.data?.avatarUrl, + snapshot.data?.displayName ?? client.userID.localpart, + size: 32, + ), ); }), ); @@ -50,11 +64,13 @@ class ChatListView extends StatelessWidget { Icons.menu, color: Theme.of(context).textTheme.bodyText1.color, ), + onSelected: controller.setActiveBundle, itemBuilder: (context) => Matrix.of(context) .accountBundles .keys .map( (bundle) => PopupMenuItem( + value: bundle, child: Text(bundle), ), ) @@ -264,22 +280,31 @@ class ChatListView extends StatelessWidget { ) : null, bottomNavigationBar: Matrix.of(context).isMultiAccount - ? SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: max( - MediaQuery.of(context).size.width, - Matrix.of(context).widget.clients.length * 84.0, - ), - child: BottomNavigationBar( - onTap: controller.setActiveClient, - currentIndex: Matrix.of(context).activeClient + - (Matrix.of(context).hasComplexBundles ? 1 : 0), - showUnselectedLabels: false, - showSelectedLabels: true, - type: BottomNavigationBarType.shifting, - selectedItemColor: Theme.of(context).primaryColor, - items: getBottomBarItems(context), + ? StreamBuilder( + stream: StreamGroup.merge(Matrix.of(context) + .widget + .clients + .map((client) => client.onSync.stream.where((s) => + s.accountData != null && + s.accountData + .any((e) => e.type == accountBundlesType)))), + builder: (context, _) => SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: max( + MediaQuery.of(context).size.width, + Matrix.of(context).widget.clients.length * 84.0, + ), + child: BottomNavigationBar( + onTap: controller.setActiveClient, + currentIndex: Matrix.of(context).activeClient + + (Matrix.of(context).hasComplexBundles ? 1 : 0), + showUnselectedLabels: false, + showSelectedLabels: true, + type: BottomNavigationBarType.shifting, + selectedItemColor: Theme.of(context).primaryColor, + items: getBottomBarItems(context), + ), ), ), ) diff --git a/lib/utils/account_bundles.dart b/lib/utils/account_bundles.dart index 83670695..eb9d96f4 100644 --- a/lib/utils/account_bundles.dart +++ b/lib/utils/account_bundles.dart @@ -43,13 +43,13 @@ class AccountBundle { }; } -const _accountBundlesType = 'im.fluffychat.account_bundles'; +const accountBundlesType = 'im.fluffychat.account_bundles'; extension AccountBundlesExtension on Client { List get accountBundles { List ret; - if (accountData.containsKey(_accountBundlesType)) { - ret = AccountBundles.fromJson(accountData[_accountBundlesType].content) + if (accountData.containsKey(accountBundlesType)) { + ret = AccountBundles.fromJson(accountData[accountBundlesType].content) .bundles; } ret ??= []; @@ -63,9 +63,10 @@ extension AccountBundlesExtension on Client { } Future setAccountBundle(String name, [int priority]) async { - final data = AccountBundles.fromJson( - accountData[_accountBundlesType]?.content ?? {}); + final data = + AccountBundles.fromJson(accountData[accountBundlesType]?.content ?? {}); var foundBundle = false; + data.bundles ??= []; for (final bundle in data.bundles) { if (bundle.name == name) { bundle.priority = priority; @@ -76,22 +77,23 @@ extension AccountBundlesExtension on Client { if (!foundBundle) { data.bundles.add(AccountBundle(name: name, priority: priority)); } - await setAccountData(userID, _accountBundlesType, data.toJson()); + await setAccountData(userID, accountBundlesType, data.toJson()); } Future removeFromAccountBundle(String name) async { - if (!accountData.containsKey(_accountBundlesType)) { + if (!accountData.containsKey(accountBundlesType)) { return; // nothing to do } final data = - AccountBundles.fromJson(accountData[_accountBundlesType].content); + AccountBundles.fromJson(accountData[accountBundlesType].content); + if (data.bundles == null) return; data.bundles.removeWhere((b) => b.name == name); - await setAccountData(userID, _accountBundlesType, data.toJson()); + await setAccountData(userID, accountBundlesType, data.toJson()); } String get sendPrefix { - final data = AccountBundles.fromJson( - accountData[_accountBundlesType]?.content ?? {}); + final data = + AccountBundles.fromJson(accountData[accountBundlesType]?.content ?? {}); return data.prefix; } } diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 449213c8..82d9bece 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -70,6 +70,9 @@ class MatrixState extends State with WidgetsBindingObserver { bool get isMultiAccount => widget.clients.length > 1; + int getClientIndexByMatrixId(String matrixId) => + widget.clients.indexWhere((client) => client.userID == matrixId); + int get _safeActiveClient { if (activeClient < 0 || activeClient >= widget.clients.length) { return 0;