diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index dcd9011a..125d3491 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1561,6 +1561,8 @@ "type": "text", "placeholders": {} }, + "addAccount": "Add account", + "enableMultiAccounts": "Enable multi accounts on this device", "openInMaps": "Open in maps", "@openInMaps": { "type": "text", diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 813b5eb7..34c302d0 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -217,12 +217,12 @@ class AppRoutes { buildTransition: _fadeTransition, stackedRoutes: [ VWidget( - path: '/login', + path: 'login', widget: Login(), buildTransition: _fadeTransition, ), VWidget( - path: '/signup', + path: 'signup', widget: SignupPage(), buildTransition: _fadeTransition, ), @@ -296,6 +296,23 @@ class AppRoutes { widget: DevicesSettings(), buildTransition: _dynamicTransition, ), + VWidget( + path: 'add', + widget: HomeserverPicker(), + buildTransition: _fadeTransition, + stackedRoutes: [ + VWidget( + path: 'login', + widget: Login(), + buildTransition: _fadeTransition, + ), + VWidget( + path: 'signup', + widget: SignupPage(), + buildTransition: _fadeTransition, + ), + ], + ), ], ), VWidget( diff --git a/lib/pages/homeserver_picker.dart b/lib/pages/homeserver_picker.dart index 4e485a24..3060089e 100644 --- a/lib/pages/homeserver_picker.dart +++ b/lib/pages/homeserver_picker.dart @@ -53,7 +53,7 @@ class HomeserverPickerController extends State { .getItem(HomeserverPickerController.ssoHomeserverKey), ); } - await Matrix.of(context).client.login( + await Matrix.of(context).createLoginClient().login( LoginType.mLoginToken, token: token, initialDeviceDisplayName: PlatformInfos.clientName, @@ -216,7 +216,7 @@ class HomeserverPickerController extends State { } } - void signUpAction() => VRouter.of(context).to('/signup'); + void signUpAction() => VRouter.of(context).to('signup'); bool _initialized = false; diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 36397d0d..dfa685a2 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -64,7 +64,7 @@ class LoginController extends State { } else { identifier = AuthenticationUserIdentifier(user: username); } - await matrix.client.login(LoginType.mLoginPassword, + await matrix.createLoginClient().login(LoginType.mLoginPassword, identifier: identifier, // To stay compatible with older server versions // ignore: deprecated_member_use diff --git a/lib/pages/settings_account.dart b/lib/pages/settings_account.dart index 83bbc392..3f3a3036 100644 --- a/lib/pages/settings_account.dart +++ b/lib/pages/settings_account.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; +import 'package:vrouter/vrouter.dart'; class SettingsAccount extends StatefulWidget { const SettingsAccount({Key key}) : super(key: key); @@ -144,6 +145,8 @@ class SettingsAccountController extends State { ); } + void addAccountAction() => VRouter.of(context).to('add'); + @override Widget build(BuildContext context) { final client = Matrix.of(context).client; diff --git a/lib/pages/signup.dart b/lib/pages/signup.dart index 69ad4f6b..b9c3ba4a 100644 --- a/lib/pages/signup.dart +++ b/lib/pages/signup.dart @@ -37,7 +37,7 @@ class SignupPageController extends State { setState(() => loading = true); try { - final client = Matrix.of(context).client; + final client = Matrix.of(context).createLoginClient(); await client.uiaRequestBackground( (auth) => client.register( username: usernameController.text, diff --git a/lib/pages/views/homeserver_picker_view.dart b/lib/pages/views/homeserver_picker_view.dart index ce9e9d80..90787b6c 100644 --- a/lib/pages/views/homeserver_picker_view.dart +++ b/lib/pages/views/homeserver_picker_view.dart @@ -128,7 +128,7 @@ class HomeserverPickerView extends StatelessWidget { Expanded( child: _LoginButton( onPressed: () => - VRouter.of(context).to('/login'), + VRouter.of(context).to('login'), icon: Icon(Icons.login_outlined), labelText: L10n.of(context).login, ), diff --git a/lib/pages/views/settings_account_view.dart b/lib/pages/views/settings_account_view.dart index 55b0a31b..b755425c 100644 --- a/lib/pages/views/settings_account_view.dart +++ b/lib/pages/views/settings_account_view.dart @@ -20,6 +20,14 @@ class SettingsAccountView extends StatelessWidget { withScrolling: true, child: Column( children: [ + if (!Matrix.of(context).isMultiAccount) + ListTile( + trailing: Icon(Icons.add_box_outlined), + title: Text(L10n.of(context).addAccount), + subtitle: Text(L10n.of(context).enableMultiAccounts), + onTap: controller.addAccountAction, + ), + Divider(height: 1), ListTile( trailing: Icon(Icons.edit_outlined), title: Text(L10n.of(context).editDisplayname), @@ -38,6 +46,7 @@ class SettingsAccountView extends StatelessWidget { title: Text(L10n.of(context).devices), onTap: () => VRouter.of(context).to('devices'), ), + Divider(height: 1), ListTile( trailing: Icon(Icons.exit_to_app_outlined), title: Text(L10n.of(context).logout), diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 9d0c5acf..0f3bde35 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -11,10 +11,9 @@ import 'matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart'; abstract class ClientManager { static const String clientNamespace = 'im.fluffychat.store.clients'; static Future> getClients() async { - final store = Store(); final clientNames = {PlatformInfos.clientName}; try { - final rawClientNames = await store.getItem(clientNamespace); + final rawClientNames = await Store().getItem(clientNamespace); if (rawClientNames != null) { final clientNamesList = (jsonDecode(rawClientNames) as List).cast(); @@ -22,27 +21,39 @@ abstract class ClientManager { } } catch (e, s) { Logs().w('Client names in store are corrupted', e, s); + await Store().deleteItem(clientNamespace); } - return clientNames - .map((clientName) => Client( - clientName, - enableE2eeRecovery: true, - verificationMethods: { - KeyVerificationMethod.numbers, - if (PlatformInfos.isMobile || PlatformInfos.isLinux) - KeyVerificationMethod.emoji, - }, - importantStateEvents: { - 'im.ponies.room_emotes', // we want emotes to work properly - }, - databaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder, - supportedLoginTypes: { - AuthenticationTypes.password, - if (PlatformInfos.isMobile || PlatformInfos.isWeb) - AuthenticationTypes.sso - }, - compute: compute, - )) - .toList(); + return clientNames.map(createClient).toList(); } + + static Future addClientNameToStore(String clientName) async { + final clientNamesList = []; + final rawClientNames = await Store().getItem(clientNamespace); + if (rawClientNames != null) { + final stored = (jsonDecode(rawClientNames) as List).cast(); + clientNamesList.addAll(stored); + } + clientNamesList.add(clientName); + await Store().setItem(clientNamespace, jsonEncode(clientNamesList)); + } + + static Client createClient(String clientName) => Client( + clientName, + enableE2eeRecovery: true, + verificationMethods: { + KeyVerificationMethod.numbers, + if (PlatformInfos.isMobile || PlatformInfos.isLinux) + KeyVerificationMethod.emoji, + }, + importantStateEvents: { + 'im.ponies.room_emotes', // we want emotes to work properly + }, + databaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder, + supportedLoginTypes: { + AuthenticationTypes.password, + if (PlatformInfos.isMobile || PlatformInfos.isWeb) + AuthenticationTypes.sso + }, + compute: compute, + ); } diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 1a8376a2..6481fa89 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:convert'; import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/utils/client_manager.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions.dart/matrix_locals.dart'; @@ -77,6 +78,26 @@ class MatrixState extends State with WidgetsBindingObserver { return _activeClient; } + Client createLoginClient() { + final multiAccount = Matrix.of(context).client.isLogged(); + final client = multiAccount + ? ClientManager.createClient( + Matrix.of(context).client.generateUniqueTransactionId()) + : Matrix.of(context).client; + if (multiAccount) { + // Add to client list + client.onLoginStateChanged.stream + .where((l) => l == LoginState.loggedIn) + .first + .then((_) { + widget.clients.add(client); + ClientManager.addClientNameToStore(client.clientName); + // TODO: Connect streamsubscriptions + }); + } + return client; + } + Map get shareContent => _shareContent; set shareContent(Map content) { _shareContent = content;