feat: Allow loading of multiple clients in main.dart
This commit is contained in:
parent
fe4dd064d1
commit
ec68d15586
26 changed files with 840 additions and 159 deletions
|
|
@ -7,10 +7,16 @@ import 'package:flutter/material.dart';
|
|||
class LoadingView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Matrix.of(context).loginState != null) {
|
||||
if (Matrix.of(context)
|
||||
.widget
|
||||
.clients
|
||||
.every((client) => client.loginState != null)) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => VRouter.of(context).to(
|
||||
Matrix.of(context).loginState == LoginState.loggedIn
|
||||
Matrix.of(context)
|
||||
.widget
|
||||
.clients
|
||||
.any((client) => client.loginState == LoginState.loggedIn)
|
||||
? '/rooms'
|
||||
: '/home',
|
||||
queryParameters: VRouter.of(context).queryParameters,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OnePageCard extends StatelessWidget {
|
||||
|
|
@ -12,7 +13,8 @@ class OnePageCard extends StatelessWidget {
|
|||
static num breakpoint = FluffyThemes.columnWidth * 2;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width <= breakpoint
|
||||
return MediaQuery.of(context).size.width <= breakpoint ||
|
||||
Matrix.of(context).client.isLogged()
|
||||
? child
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import 'package:fluffychat/pages/views/empty_page_view.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../matrix.dart';
|
||||
|
||||
class WaitForInitPage extends StatelessWidget {
|
||||
final Widget page;
|
||||
const WaitForInitPage(this.page, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Matrix.of(context).loginState == null) {
|
||||
return StreamBuilder<LoginState>(
|
||||
stream: Matrix.of(context).client.onLoginStateChanged.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return EmptyPage(loading: true);
|
||||
}
|
||||
return page;
|
||||
});
|
||||
}
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -23,6 +24,7 @@ import '../pages/key_verification_dialog.dart';
|
|||
import '../utils/platform_infos.dart';
|
||||
import '../config/app_config.dart';
|
||||
import '../config/setting_keys.dart';
|
||||
import '../utils/account_bundles.dart';
|
||||
import '../utils/background_push.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
|
|
@ -35,7 +37,7 @@ class Matrix extends StatefulWidget {
|
|||
|
||||
final BuildContext context;
|
||||
|
||||
final Client client;
|
||||
final List<Client> clients;
|
||||
|
||||
final Map<String, String> queryParameters;
|
||||
|
||||
|
|
@ -43,7 +45,7 @@ class Matrix extends StatefulWidget {
|
|||
this.child,
|
||||
@required this.router,
|
||||
@required this.context,
|
||||
@required this.client,
|
||||
@required this.clients,
|
||||
this.queryParameters,
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
|
@ -57,12 +59,98 @@ class Matrix extends StatefulWidget {
|
|||
}
|
||||
|
||||
class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||
Client get client => widget.client;
|
||||
int activeClient = 0;
|
||||
String activeBundle;
|
||||
Store store = Store();
|
||||
BuildContext navigatorContext;
|
||||
|
||||
BackgroundPush _backgroundPush;
|
||||
|
||||
Client get client => widget.clients[_safeActiveClient];
|
||||
|
||||
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;
|
||||
}
|
||||
return activeClient;
|
||||
}
|
||||
|
||||
void setActiveClient(Client cl) {
|
||||
final i = widget.clients.indexWhere((c) => c == cl);
|
||||
if (i != null) {
|
||||
activeClient = i;
|
||||
} else {
|
||||
Logs().w('Tried to set an unknown client ${cl.userID} as active');
|
||||
}
|
||||
}
|
||||
|
||||
List<Client> get currentBundle {
|
||||
if (!hasComplexBundles) {
|
||||
return List.from(widget.clients);
|
||||
}
|
||||
final bundles = accountBundles;
|
||||
if (bundles.containsKey(activeBundle)) {
|
||||
return bundles[activeBundle];
|
||||
}
|
||||
return bundles.values.first;
|
||||
}
|
||||
|
||||
Map<String, List<Client>> get accountBundles {
|
||||
final resBundles = <String, List<_AccountBundleWithClient>>{};
|
||||
for (var i = 0; i < widget.clients.length; i++) {
|
||||
final bundles = widget.clients[i].accountBundles;
|
||||
for (final bundle in bundles) {
|
||||
if (bundle.name == null) {
|
||||
continue;
|
||||
}
|
||||
resBundles[bundle.name] ??= [];
|
||||
resBundles[bundle.name].add(_AccountBundleWithClient(
|
||||
client: widget.clients[i],
|
||||
bundle: bundle,
|
||||
));
|
||||
}
|
||||
}
|
||||
for (final b in resBundles.values) {
|
||||
b.sort((a, b) => a.bundle.priority == null
|
||||
? 1
|
||||
: b.bundle.priority == null
|
||||
? -1
|
||||
: a.bundle.priority.compareTo(b.bundle.priority));
|
||||
}
|
||||
return resBundles
|
||||
.map((k, v) => MapEntry(k, v.map((vv) => vv.client).toList()));
|
||||
}
|
||||
|
||||
bool get hasComplexBundles => accountBundles.values.any((v) => v.length > 1);
|
||||
|
||||
Client _loginClientCandidate;
|
||||
|
||||
Client getLoginClient() {
|
||||
final multiAccount = client.isLogged();
|
||||
if (!multiAccount) return client;
|
||||
_loginClientCandidate ??= ClientManager.createClient(
|
||||
client.generateUniqueTransactionId())
|
||||
..onLoginStateChanged
|
||||
.stream
|
||||
.where((l) => l == LoginState.loggedIn)
|
||||
.first
|
||||
.then((_) {
|
||||
widget.clients.add(_loginClientCandidate);
|
||||
ClientManager.addClientNameToStore(_loginClientCandidate.clientName);
|
||||
_registerSubs(_loginClientCandidate.clientName);
|
||||
widget.router.currentState.to('/rooms');
|
||||
});
|
||||
return _loginClientCandidate;
|
||||
}
|
||||
|
||||
Client getClientByName(String name) => widget.clients
|
||||
.firstWhere((c) => c.clientName == name, orElse: () => null);
|
||||
|
||||
Map<String, dynamic> get shareContent => _shareContent;
|
||||
set shareContent(Map<String, dynamic> content) {
|
||||
_shareContent = content;
|
||||
|
|
@ -78,8 +166,8 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
|
||||
void _initWithStore() async {
|
||||
try {
|
||||
await client.init();
|
||||
if (client.isLogged()) {
|
||||
// TODO: Figure out how this works in multi account
|
||||
final statusMsg = await store.getItem(SettingKeys.ownStatusMessage);
|
||||
if (statusMsg?.isNotEmpty ?? false) {
|
||||
Logs().v('Send cached status message: "$statusMsg"');
|
||||
|
|
@ -97,15 +185,15 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
|
||||
StreamSubscription onRoomKeyRequestSub;
|
||||
StreamSubscription onKeyVerificationRequestSub;
|
||||
StreamSubscription onJitsiCallSub;
|
||||
StreamSubscription onNotification;
|
||||
StreamSubscription<LoginState> onLoginStateChanged;
|
||||
StreamSubscription<UiaRequest> onUiaRequest;
|
||||
final onRoomKeyRequestSub = <String, StreamSubscription>{};
|
||||
final onKeyVerificationRequestSub = <String, StreamSubscription>{};
|
||||
final onJitsiCallSub = <String, StreamSubscription>{};
|
||||
final onNotification = <String, StreamSubscription>{};
|
||||
final onLoginStateChanged = <String, StreamSubscription<LoginState>>{};
|
||||
final onUiaRequest = <String, StreamSubscription<UiaRequest>>{};
|
||||
StreamSubscription<html.Event> onFocusSub;
|
||||
StreamSubscription<html.Event> onBlurSub;
|
||||
StreamSubscription<Presence> onOwnPresence;
|
||||
final onOwnPresence = <String, StreamSubscription<Presence>>{};
|
||||
|
||||
String _cachedPassword;
|
||||
String get cachedPassword {
|
||||
|
|
@ -165,14 +253,12 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
return uiaRequest
|
||||
.cancel(Exception(L10n.of(context).serverRequiresEmail));
|
||||
}
|
||||
final clientSecret =
|
||||
Matrix.of(context).client.generateUniqueTransactionId();
|
||||
final currentThreepidCreds =
|
||||
await Matrix.of(context).client.requestTokenToRegisterEmail(
|
||||
clientSecret,
|
||||
emailInput.single,
|
||||
0,
|
||||
);
|
||||
final clientSecret = client.generateUniqueTransactionId();
|
||||
final currentThreepidCreds = await client.requestTokenToRegisterEmail(
|
||||
clientSecret,
|
||||
emailInput.single,
|
||||
0,
|
||||
);
|
||||
final auth = AuthenticationThreePidCreds(
|
||||
session: uiaRequest.session,
|
||||
type: AuthenticationTypes.emailIdentity,
|
||||
|
|
@ -289,21 +375,14 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
|
||||
LoginState loginState;
|
||||
|
||||
void initMatrix() {
|
||||
// Display the app lock
|
||||
if (PlatformInfos.isMobile) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FlutterSecureStorage().read(key: SettingKeys.appLockKey).then((lock) {
|
||||
if (lock?.isNotEmpty ?? false) {
|
||||
AppLock.of(widget.context).enable();
|
||||
AppLock.of(widget.context).showLockScreen();
|
||||
}
|
||||
});
|
||||
});
|
||||
void _registerSubs(String name) {
|
||||
final c = getClientByName(name);
|
||||
if (c == null) {
|
||||
Logs().w(
|
||||
'Attempted to register subscriptions for non-existing client $name');
|
||||
return;
|
||||
}
|
||||
onKeyVerificationRequestSub ??= client.onKeyVerificationRequest.stream
|
||||
onKeyVerificationRequestSub[name] ??= c.onKeyVerificationRequest.stream
|
||||
.listen((KeyVerification request) async {
|
||||
var hidPopup = false;
|
||||
request.onUpdate = () {
|
||||
|
|
@ -334,51 +413,95 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
await request.rejectVerification();
|
||||
}
|
||||
});
|
||||
_initWithStore();
|
||||
|
||||
if (kIsWeb) {
|
||||
onFocusSub = html.window.onFocus.listen((_) => webHasFocus = true);
|
||||
onBlurSub = html.window.onBlur.listen((_) => webHasFocus = false);
|
||||
}
|
||||
onLoginStateChanged ??= client.onLoginStateChanged.stream.listen((state) {
|
||||
if (loginState != state) {
|
||||
loginState = state;
|
||||
final isInLoginRoutes = {'/home', '/login', '/signup'}
|
||||
.contains(widget.router.currentState.url);
|
||||
if (widget.router.currentState.url == '/' ||
|
||||
(state == LoginState.loggedIn) == isInLoginRoutes) {
|
||||
onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) {
|
||||
final loggedInWithMultipleClients = widget.clients.length > 1;
|
||||
if (state != LoginState.loggedIn) {
|
||||
_cancelSubs(c.clientName);
|
||||
widget.clients.remove(c);
|
||||
}
|
||||
if (loggedInWithMultipleClients) {
|
||||
// TODO: display a nicer toast
|
||||
showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: navigatorContext,
|
||||
title: 'Login state of client $name changed',
|
||||
message: 'New login state: $state',
|
||||
okLabel: L10n.of(widget.context).ok,
|
||||
);
|
||||
if (state != LoginState.loggedIn) {
|
||||
widget.router.currentState.to(
|
||||
loginState == LoginState.loggedIn ? '/rooms' : '/home',
|
||||
'/rooms',
|
||||
queryParameters: widget.router.currentState.queryParameters,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
widget.router.currentState.to(
|
||||
state == LoginState.loggedIn ? '/rooms' : '/home',
|
||||
queryParameters: widget.router.currentState.queryParameters,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Cache and resend status message
|
||||
onOwnPresence ??= client.onPresence.stream.listen((presence) {
|
||||
if (client.isLogged() &&
|
||||
client.userID == presence.senderId &&
|
||||
onOwnPresence[name] ??= c.onPresence.stream.listen((presence) {
|
||||
if (c.isLogged() &&
|
||||
c.userID == presence.senderId &&
|
||||
presence.presence?.statusMsg != null) {
|
||||
Logs().v('Update status message: "${presence.presence.statusMsg}"');
|
||||
store.setItem(
|
||||
SettingKeys.ownStatusMessage, presence.presence.statusMsg);
|
||||
}
|
||||
});
|
||||
|
||||
onUiaRequest ??= client.onUiaRequest.stream.listen(_onUiaRequest);
|
||||
onUiaRequest[name] ??= c.onUiaRequest.stream.listen(_onUiaRequest);
|
||||
if (PlatformInfos.isWeb || PlatformInfos.isLinux) {
|
||||
client.onSync.stream.first.then((s) {
|
||||
c.onSync.stream.first.then((s) {
|
||||
html.Notification.requestPermission();
|
||||
onNotification ??= client.onEvent.stream
|
||||
onNotification[name] ??= c.onEvent.stream
|
||||
.where((e) =>
|
||||
e.type == EventUpdateType.timeline &&
|
||||
[EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted]
|
||||
.contains(e.content['type']) &&
|
||||
e.content['sender'] != client.userID)
|
||||
e.content['sender'] != c.userID)
|
||||
.listen(_showLocalNotification);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelSubs(String name) {
|
||||
onRoomKeyRequestSub[name]?.cancel();
|
||||
onRoomKeyRequestSub.remove(name);
|
||||
onKeyVerificationRequestSub[name]?.cancel();
|
||||
onKeyVerificationRequestSub.remove(name);
|
||||
onLoginStateChanged[name]?.cancel();
|
||||
onLoginStateChanged.remove(name);
|
||||
onOwnPresence[name]?.cancel();
|
||||
onOwnPresence.remove(name);
|
||||
onNotification[name]?.cancel();
|
||||
onNotification.remove(name);
|
||||
}
|
||||
|
||||
void initMatrix() {
|
||||
// Display the app lock
|
||||
if (PlatformInfos.isMobile) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FlutterSecureStorage().read(key: SettingKeys.appLockKey).then((lock) {
|
||||
if (lock?.isNotEmpty ?? false) {
|
||||
AppLock.of(widget.context).enable();
|
||||
AppLock.of(widget.context).showLockScreen();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_initWithStore();
|
||||
|
||||
for (final c in widget.clients) {
|
||||
_registerSubs(c.clientName);
|
||||
}
|
||||
|
||||
if (kIsWeb) {
|
||||
onFocusSub = html.window.onFocus.listen((_) => webHasFocus = true);
|
||||
onBlurSub = html.window.onBlur.listen((_) => webHasFocus = false);
|
||||
}
|
||||
|
||||
if (PlatformInfos.isMobile) {
|
||||
_backgroundPush = BackgroundPush(
|
||||
|
|
@ -445,11 +568,12 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
||||
onRoomKeyRequestSub?.cancel();
|
||||
onKeyVerificationRequestSub?.cancel();
|
||||
onLoginStateChanged?.cancel();
|
||||
onOwnPresence?.cancel();
|
||||
onNotification?.cancel();
|
||||
onRoomKeyRequestSub.values.map((s) => s.cancel());
|
||||
onKeyVerificationRequestSub.values.map((s) => s.cancel());
|
||||
onLoginStateChanged.values.map((s) => s.cancel());
|
||||
onOwnPresence.values.map((s) => s.cancel());
|
||||
onNotification.values.map((s) => s.cancel());
|
||||
|
||||
onFocusSub?.cancel();
|
||||
onBlurSub?.cancel();
|
||||
_backgroundPush?.onLogin?.cancel();
|
||||
|
|
@ -491,3 +615,9 @@ class FixedThreepidCreds extends ThreepidCreds {
|
|||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountBundleWithClient {
|
||||
final Client client;
|
||||
final AccountBundle bundle;
|
||||
_AccountBundleWithClient({this.client, this.bundle});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue