implement vodozemac (aka steal 50% code from fluffychat)

This commit is contained in:
OfficialDakari 2025-06-12 18:33:09 +05:00
parent 0c7e9a132e
commit 39b7c99a44
14 changed files with 180 additions and 130 deletions

View File

@ -11,6 +11,8 @@ abstract class AppConfig {
static String? get applicationWelcomeMessage => _applicationWelcomeMessage; static String? get applicationWelcomeMessage => _applicationWelcomeMessage;
static String _defaultHomeserver = 'extera.xyz'; static String _defaultHomeserver = 'extera.xyz';
static bool displayNavigationRail = true;
static String get defaultHomeserver => _defaultHomeserver; static String get defaultHomeserver => _defaultHomeserver;
static double fontSizeFactor = 1; static double fontSizeFactor = 1;
static const Color chatColor = primaryColor; static const Color chatColor = primaryColor;

View File

@ -37,6 +37,7 @@ import 'package:fluffychat/widgets/layouts/two_column_layout.dart';
import 'package:fluffychat/widgets/log_view.dart'; import 'package:fluffychat/widgets/log_view.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/share_scaffold_dialog.dart'; import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
import 'package:matrix/matrix.dart';
abstract class AppRoutes { abstract class AppRoutes {
static FutureOr<String?> loggedInRedirect( static FutureOr<String?> loggedInRedirect(
@ -73,7 +74,7 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder( pageBuilder: (context, state) => defaultPageBuilder(
context, context,
state, state,
const Login(), Login(client: state.extra as Client),
), ),
redirect: loggedInRedirect, redirect: loggedInRedirect,
), ),
@ -260,7 +261,9 @@ abstract class AppRoutes {
pageBuilder: (context, state) => defaultPageBuilder( pageBuilder: (context, state) => defaultPageBuilder(
context, context,
state, state,
const Login(), Login(
client: state.extra as Client,
),
), ),
redirect: loggedOutRedirect, redirect: loggedOutRedirect,
), ),

View File

@ -1,6 +1,7 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
abstract class SettingKeys { abstract class SettingKeys {
static const String displayNavigationRail = 'chat.fluffy.displayNavigationRail';
static const String hideAvatarsInInvites = 'xyz.extera.next.hideAvatarsInInvites'; static const String hideAvatarsInInvites = 'xyz.extera.next.hideAvatarsInInvites';
static const String pureBlack = 'xyz.extera.next.pureBlack'; static const String pureBlack = 'xyz.extera.next.pureBlack';
static const String renderHtml = 'chat.fluffy.renderHtml'; static const String renderHtml = 'chat.fluffy.renderHtml';
@ -43,6 +44,7 @@ enum AppSettings<T> {
audioRecordingNoiseSuppress<bool>('audioRecordingNoiseSuppress', true), audioRecordingNoiseSuppress<bool>('audioRecordingNoiseSuppress', true),
audioRecordingBitRate<int>('audioRecordingBitRate', 64000), audioRecordingBitRate<int>('audioRecordingBitRate', 64000),
audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100), audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100),
enableSoftLogout<bool>('enableSoftLogout', true),
pushNotificationsGatewayUrl<String>( pushNotificationsGatewayUrl<String>(
'pushNotificationsGatewayUrl', 'pushNotificationsGatewayUrl',
'https://push.fluffychat.im/_matrix/push/v1/notify', 'https://push.fluffychat.im/_matrix/push/v1/notify',

View File

@ -5,6 +5,8 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -21,6 +23,8 @@ void main() async {
// widget bindings are initialized already. // widget bindings are initialized already.
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await vod.init(wasmPath: './assets/assets/vodozemac/');
Logs().nativeColors = !PlatformInfos.isIOS; Logs().nativeColors = !PlatformInfos.isIOS;
final store = await SharedPreferences.getInstance(); final store = await SharedPreferences.getInstance();
final clients = await ClientManager.getClients(store: store); final clients = await ClientManager.getClients(store: store);

View File

@ -1,10 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
@ -69,10 +69,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-'); homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
if (homeserverInput.isEmpty) { if (homeserverInput.isEmpty) {
final client = await Matrix.of(context).getLoginClient();
setState(() { setState(() {
error = loginFlows = null; error = loginFlows = null;
isLoading = false; isLoading = false;
Matrix.of(context).getLoginClient().homeserver = null; client.homeserver = null;
}); });
return; return;
} }
@ -88,7 +89,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
if (homeserver.scheme.isEmpty) { if (homeserver.scheme.isEmpty) {
homeserver = Uri.https(homeserverInput, ''); homeserver = Uri.https(homeserverInput, '');
} }
final client = Matrix.of(context).getLoginClient(); final client = await Matrix.of(context).getLoginClient();
final (_, _, loginFlows) = await client.checkHomeserver(homeserver); final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
this.loginFlows = loginFlows; this.loginFlows = loginFlows;
if (supportsSso && !legacyPasswordLogin) { if (supportsSso && !legacyPasswordLogin) {
@ -105,6 +106,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} }
context.push( context.push(
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login', '${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
extra: client,
); );
} catch (e) { } catch (e) {
setState( setState(
@ -142,8 +144,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
: isDefaultPlatform : isDefaultPlatform
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login' ? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
: 'http://localhost:3001//login'; : 'http://localhost:3001//login';
final client = await Matrix.of(context).getLoginClient();
final url = Matrix.of(context).getLoginClient().homeserver!.replace( final url = client.homeserver!.replace(
path: '/_matrix/client/v3/login/sso/redirect', path: '/_matrix/client/v3/login/sso/redirect',
queryParameters: {'redirectUrl': redirectUrl}, queryParameters: {'redirectUrl': redirectUrl},
); );
@ -164,11 +166,12 @@ class HomeserverPickerController extends State<HomeserverPicker> {
isLoading = true; isLoading = true;
}); });
try { try {
await Matrix.of(context).getLoginClient().login( final client = await Matrix.of(context).getLoginClient();
LoginType.mLoginToken, client.login(
token: token, LoginType.mLoginToken,
initialDeviceDisplayName: PlatformInfos.clientName, token: token,
); initialDeviceDisplayName: PlatformInfos.clientName,
);
} catch (e) { } catch (e) {
setState(() { setState(() {
error = e.toLocalizedString(context); error = e.toLocalizedString(context);
@ -200,7 +203,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
isLoading = true; isLoading = true;
}); });
try { try {
final client = Matrix.of(context).getLoginClient(); final client = await Matrix.of(context).getLoginClient();
await client.importDump(String.fromCharCodes(await file.readAsBytes())); await client.importDump(String.fromCharCodes(await file.readAsBytes()));
Matrix.of(context).initMatrix(); Matrix.of(context).initMatrix();
} catch (e) { } catch (e) {
@ -245,4 +248,4 @@ class IdentityProvider {
icon: json['icon'], icon: json['icon'],
brand: json['brand'], brand: json['brand'],
); );
} }

View File

@ -2,9 +2,9 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
@ -14,7 +14,8 @@ import '../../utils/platform_infos.dart';
import 'login_view.dart'; import 'login_view.dart';
class Login extends StatefulWidget { class Login extends StatefulWidget {
const Login({super.key}); final Client client;
const Login({required this.client, super.key});
@override @override
LoginController createState() => LoginController(); LoginController createState() => LoginController();
@ -68,17 +69,18 @@ class LoginController extends State<Login> {
} else { } else {
identifier = AuthenticationUserIdentifier(user: username); identifier = AuthenticationUserIdentifier(user: username);
} }
await matrix.getLoginClient().login( final client = await matrix.getLoginClient();
LoginType.mLoginPassword, await client.login(
identifier: identifier, LoginType.mLoginPassword,
// To stay compatible with older server versions identifier: identifier,
// ignore: deprecated_member_use // To stay compatible with older server versions
user: identifier.type == AuthenticationIdentifierTypes.userId // ignore: deprecated_member_use
? username user: identifier.type == AuthenticationIdentifierTypes.userId
: null, ? username
password: passwordController.text, : null,
initialDeviceDisplayName: PlatformInfos.clientName, password: passwordController.text,
); initialDeviceDisplayName: PlatformInfos.clientName,
);
} on MatrixException catch (exception) { } on MatrixException catch (exception) {
setState(() => passwordError = exception.errorMessage); setState(() => passwordError = exception.errorMessage);
return setState(() => loading = false); return setState(() => loading = false);
@ -103,14 +105,13 @@ class LoginController extends State<Login> {
void _checkWellKnown(String userId) async { void _checkWellKnown(String userId) async {
if (mounted) setState(() => usernameError = null); if (mounted) setState(() => usernameError = null);
if (!userId.isValidMatrixId) return; if (!userId.isValidMatrixId) return;
final oldHomeserver = Matrix.of(context).getLoginClient().homeserver; final oldHomeserver = widget.client.homeserver;
try { try {
var newDomain = Uri.https(userId.domain!, ''); var newDomain = Uri.https(userId.domain!, '');
Matrix.of(context).getLoginClient().homeserver = newDomain; widget.client.homeserver = newDomain;
DiscoveryInformation? wellKnownInformation; DiscoveryInformation? wellKnownInformation;
try { try {
wellKnownInformation = wellKnownInformation = await widget.client.getWellknown();
await Matrix.of(context).getLoginClient().getWellknown();
if (wellKnownInformation.mHomeserver.baseUrl.toString().isNotEmpty) { if (wellKnownInformation.mHomeserver.baseUrl.toString().isNotEmpty) {
newDomain = wellKnownInformation.mHomeserver.baseUrl; newDomain = wellKnownInformation.mHomeserver.baseUrl;
} }
@ -118,10 +119,10 @@ class LoginController extends State<Login> {
// do nothing, newDomain is already set to a reasonable fallback // do nothing, newDomain is already set to a reasonable fallback
} }
if (newDomain != oldHomeserver) { if (newDomain != oldHomeserver) {
await Matrix.of(context).getLoginClient().checkHomeserver(newDomain); await widget.client.checkHomeserver(newDomain);
if (Matrix.of(context).getLoginClient().homeserver == null) { if (widget.client.homeserver == null) {
Matrix.of(context).getLoginClient().homeserver = oldHomeserver; widget.client.homeserver = oldHomeserver;
// okay, the server we checked does not appear to be a matrix server // okay, the server we checked does not appear to be a matrix server
Logs().v( Logs().v(
'$newDomain is not running a homeserver, asking to use $oldHomeserver', '$newDomain is not running a homeserver, asking to use $oldHomeserver',
@ -144,13 +145,13 @@ class LoginController extends State<Login> {
usernameError = null; usernameError = null;
if (mounted) setState(() {}); if (mounted) setState(() {});
} else { } else {
Matrix.of(context).getLoginClient().homeserver = oldHomeserver; widget.client.homeserver = oldHomeserver;
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
} }
} catch (e) { } catch (e) {
Matrix.of(context).getLoginClient().homeserver = oldHomeserver; widget.client.homeserver = oldHomeserver;
usernameError = e.toLocalizedString(context); usernameError = e.toLocalizedString(context);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
@ -173,12 +174,11 @@ class LoginController extends State<Login> {
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString(); final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
final response = await showFutureLoadingDialog( final response = await showFutureLoadingDialog(
context: context, context: context,
future: () => future: () => widget.client.requestTokenToResetPasswordEmail(
Matrix.of(context).getLoginClient().requestTokenToResetPasswordEmail( clientSecret,
clientSecret, input,
input, sendAttempt++,
sendAttempt++, ),
),
); );
if (response.error != null) return; if (response.error != null) return;
final password = await showTextInputDialog( final password = await showTextInputDialog(
@ -215,11 +215,11 @@ class LoginController extends State<Login> {
}; };
final success = await showFutureLoadingDialog( final success = await showFutureLoadingDialog(
context: context, context: context,
future: () => Matrix.of(context).getLoginClient().request( future: () => widget.client.request(
RequestType.POST, RequestType.POST,
'/client/v3/account/password', '/client/v3/account/password',
data: data, data: data,
), ),
); );
if (success.error == null) { if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -245,4 +245,4 @@ extension on String {
bool get isEmail => _emailRegex.hasMatch(this); bool get isEmail => _emailRegex.hasMatch(this);
bool get isPhoneNumber => _phoneRegex.hasMatch(this); bool get isPhoneNumber => _phoneRegex.hasMatch(this);
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluffychat/generated/l10n/l10n.dart'; import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'login.dart'; import 'login.dart';
@ -15,16 +14,15 @@ class LoginView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final homeserver = Matrix.of(context) final homeserver = controller.widget.client.homeserver
.getLoginClient()
.homeserver
.toString() .toString()
.replaceFirst('https://', ''); .replaceFirst('https://', '');
final title = L10n.of(context).logInTo(homeserver); final title = L10n.of(context).logInTo(homeserver);
final titleParts = title.split(homeserver); final titleParts = title.split(homeserver);
return LoginScaffold( return LoginScaffold(
enforceMobileMode: Matrix.of(context).client.isLogged(), enforceMobileMode:
Matrix.of(context).widget.clients.any((client) => client.isLogged()),
appBar: AppBar( appBar: AppBar(
leading: controller.loading ? null : const Center(child: BackButton()), leading: controller.loading ? null : const Center(child: BackButton()),
automaticallyImplyLeading: !controller.loading, automaticallyImplyLeading: !controller.loading,
@ -140,4 +138,4 @@ class LoginView extends StatelessWidget {
), ),
); );
} }
} }

View File

@ -1,11 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:desktop_notifications/desktop_notifications.dart'; import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/encryption/utils/key_verification.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -46,7 +47,7 @@ abstract class ClientManager {
await store.setStringList(clientNamespace, clientNames.toList()); await store.setStringList(clientNamespace, clientNames.toList());
} }
final clients = final clients =
clientNames.map((name) => createClient(name, store)).toList(); await Future.wait(clientNames.map((name) => createClient(name, store)));
if (initialize) { if (initialize) {
await Future.wait( await Future.wait(
clients.map( clients.map(
@ -98,10 +99,17 @@ abstract class ClientManager {
static NativeImplementations get nativeImplementations => kIsWeb static NativeImplementations get nativeImplementations => kIsWeb
? const NativeImplementationsDummy() ? const NativeImplementationsDummy()
: NativeImplementationsIsolate(compute); : NativeImplementationsIsolate(
compute,
vodozemacInit: () => vod.init(wasmPath: './assets/assets/vodozemac/'),
);
static Client createClient(String clientName, SharedPreferences store) { static Future<Client> createClient(
String clientName,
SharedPreferences store,
) async {
final shareKeysWith = AppSettings.shareKeysWith.getItem(store); final shareKeysWith = AppSettings.shareKeysWith.getItem(store);
final enableSoftLogout = AppSettings.enableSoftLogout.getItem(store);
return Client( return Client(
clientName, clientName,
@ -117,8 +125,7 @@ abstract class ClientManager {
'im.ponies.room_emotes', 'im.ponies.room_emotes',
}, },
logLevel: kReleaseMode ? Level.warning : Level.verbose, logLevel: kReleaseMode ? Level.warning : Level.verbose,
//database: flutterMatrixSdkDatabaseBuilder(client), database: await flutterMatrixSdkDatabaseBuilder(clientName),
databaseBuilder: flutterMatrixSdkDatabaseBuilder,
supportedLoginTypes: { supportedLoginTypes: {
AuthenticationTypes.password, AuthenticationTypes.password,
AuthenticationTypes.sso, AuthenticationTypes.sso,
@ -131,6 +138,8 @@ abstract class ClientManager {
.singleWhereOrNull((share) => share.name == shareKeysWith) ?? .singleWhereOrNull((share) => share.name == shareKeysWith) ??
ShareKeysWith.all, ShareKeysWith.all,
convertLinebreaksInFormatting: false, convertLinebreaksInFormatting: false,
onSoftLogout:
enableSoftLogout ? (client) => client.refreshAccessToken() : null,
); );
} }
@ -178,4 +187,4 @@ abstract class ClientManager {
), ),
); );
} }
} }

View File

@ -1,8 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -10,6 +10,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'cipher.dart'; import 'cipher.dart';
@ -17,10 +18,10 @@ import 'cipher.dart';
import 'sqlcipher_stub.dart' import 'sqlcipher_stub.dart'
if (dart.library.io) 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart'; if (dart.library.io) 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';
Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async { Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(String clientName) async {
MatrixSdkDatabase? database; MatrixSdkDatabase? database;
try { try {
database = await _constructDatabase(client); database = await _constructDatabase(clientName);
await database.open(); await database.open();
return database; return database;
} catch (e, s) { } catch (e, s) {
@ -36,7 +37,7 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
// Delete database file: // Delete database file:
if (database == null && !kIsWeb) { if (database == null && !kIsWeb) {
final dbFile = File(await _getDatabasePath(client.clientName)); final dbFile = File(await _getDatabasePath(clientName));
if (await dbFile.exists()) await dbFile.delete(); if (await dbFile.exists()) await dbFile.delete();
} }
@ -54,16 +55,14 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
Logs().e('Unable to send error notification', e, s); Logs().e('Unable to send error notification', e, s);
} }
database = await _constructDatabase(client); rethrow;
await database.open();
return database;
} }
} }
Future<MatrixSdkDatabase> _constructDatabase(Client client) async { Future<MatrixSdkDatabase> _constructDatabase(String clientName) async {
if (kIsWeb) { if (kIsWeb) {
html.window.navigator.storage?.persist(); html.window.navigator.storage?.persist();
return MatrixSdkDatabase(client.clientName); return await MatrixSdkDatabase.init(clientName);
} }
final cipher = await getDatabaseCipher(); final cipher = await getDatabaseCipher();
@ -77,7 +76,7 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
); );
} }
final path = await _getDatabasePath(client.clientName); final path = await _getDatabasePath(clientName);
// fix dlopen for old Android // fix dlopen for old Android
await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions(); await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
@ -86,7 +85,7 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit); createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit);
// migrate from potential previous SQLite database path to current one // migrate from potential previous SQLite database path to current one
await _migrateLegacyLocation(path, client.clientName); await _migrateLegacyLocation(path, clientName);
// required for [getDatabasesPath] // required for [getDatabasesPath]
databaseFactory = factory; databaseFactory = factory;
@ -113,8 +112,8 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
), ),
); );
return MatrixSdkDatabase( return await MatrixSdkDatabase.init(
client.clientName, clientName,
database: database, database: database,
maxFileSize: 1000 * 1000 * 10, maxFileSize: 1000 * 1000 * 10,
fileStorageLocation: fileStorageLocation?.uri, fileStorageLocation: fileStorageLocation?.uri,
@ -149,4 +148,4 @@ Future<void> _migrateLegacyLocation(
await maybeOldFile.copy(sqlFilePath); await maybeOldFile.copy(sqlFilePath);
await maybeOldFile.delete(); await maybeOldFile.delete();
} }
} }

View File

@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:desktop_notifications/desktop_notifications.dart'; import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/init_with_restore.dart'; import 'package:fluffychat/utils/init_with_restore.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
@ -73,9 +74,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
BackgroundPush? backgroundPush; BackgroundPush? backgroundPush;
Client get client { Client get client {
if (widget.clients.isEmpty) {
widget.clients.add(getLoginClient());
}
if (_activeClient < 0 || _activeClient >= widget.clients.length) { if (_activeClient < 0 || _activeClient >= widget.clients.length) {
return currentBundle!.first!; return currentBundle!.first!;
} }
@ -148,29 +146,35 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
Client? _loginClientCandidate; Client? _loginClientCandidate;
Client getLoginClient() { AudioPlayer? audioPlayer;
final ValueNotifier<String?> voiceMessageEventId = ValueNotifier(null);
Future<Client> getLoginClient() async {
if (widget.clients.isNotEmpty && !client.isLogged()) { if (widget.clients.isNotEmpty && !client.isLogged()) {
return client; return client;
} }
final candidate = _loginClientCandidate ??= ClientManager.createClient( final candidate =
_loginClientCandidate ??= await ClientManager.createClient(
'${AppConfig.applicationName}-${DateTime.now().millisecondsSinceEpoch}', '${AppConfig.applicationName}-${DateTime.now().millisecondsSinceEpoch}',
store, store,
)..onLoginStateChanged )
.stream ..onLoginStateChanged
.where((l) => l == LoginState.loggedIn) .stream
.first .where((l) => l == LoginState.loggedIn)
.then((_) { .first
if (!widget.clients.contains(_loginClientCandidate)) { .then((_) {
widget.clients.add(_loginClientCandidate!); if (!widget.clients.contains(_loginClientCandidate)) {
} widget.clients.add(_loginClientCandidate!);
ClientManager.addClientNameToStore( }
_loginClientCandidate!.clientName, ClientManager.addClientNameToStore(
store, _loginClientCandidate!.clientName,
); store,
_registerSubs(_loginClientCandidate!.clientName); );
_loginClientCandidate = null; _registerSubs(_loginClientCandidate!.clientName);
FluffyChatApp.router.go('/rooms'); _loginClientCandidate = null;
}); FluffyChatApp.router.go('/rooms');
});
if (widget.clients.isEmpty) widget.clients.add(candidate);
return candidate; return candidate;
} }
@ -279,12 +283,12 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) { onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) {
final loggedInWithMultipleClients = widget.clients.length > 1; final loggedInWithMultipleClients = widget.clients.length > 1;
if (state == LoginState.loggedOut) { if (state == LoginState.loggedOut) {
InitWithRestoreExtension.deleteSessionBackup(name);
}
if (loggedInWithMultipleClients && state != LoginState.loggedIn) {
_cancelSubs(c.clientName); _cancelSubs(c.clientName);
widget.clients.remove(c); widget.clients.remove(c);
ClientManager.removeClientNameFromStore(c.clientName, store); ClientManager.removeClientNameFromStore(c.clientName, store);
InitWithRestoreExtension.deleteSessionBackup(name);
}
if (loggedInWithMultipleClients && state != LoginState.loggedIn) {
ScaffoldMessenger.of( ScaffoldMessenger.of(
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ?? FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ??
context, context,
@ -376,12 +380,14 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
Logs().v('AppLifecycleState = $state'); Logs().v('AppLifecycleState = $state');
final foreground = state != AppLifecycleState.inactive && final foreground = state != AppLifecycleState.inactive &&
state != AppLifecycleState.paused; state != AppLifecycleState.paused;
client.syncPresence = for (final client in widget.clients) {
state == AppLifecycleState.resumed ? null : PresenceType.unavailable; client.syncPresence =
if (PlatformInfos.isMobile) { state == AppLifecycleState.resumed ? null : PresenceType.unavailable;
client.backgroundSync = foreground; if (PlatformInfos.isMobile) {
client.requestHistoryOnLimitedTimeline = !foreground; client.backgroundSync = foreground;
Logs().v('Set background sync to', foreground); client.requestHistoryOnLimitedTimeline = !foreground;
Logs().v('Set background sync to', foreground);
}
} }
} }
@ -432,6 +438,10 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
AppConfig.showPresences = AppConfig.showPresences =
store.getBool(SettingKeys.showPresences) ?? AppConfig.showPresences; store.getBool(SettingKeys.showPresences) ?? AppConfig.showPresences;
AppConfig.displayNavigationRail =
store.getBool(SettingKeys.displayNavigationRail) ??
AppConfig.displayNavigationRail;
} }
@override @override
@ -493,4 +503,4 @@ class _AccountBundleWithClient {
final AccountBundle? bundle; final AccountBundle? bundle;
_AccountBundleWithClient({this.client, this.bundle}); _AccountBundleWithClient({this.client, this.bundle});
} }

View File

@ -19,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_vodozemac
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)

View File

@ -137,6 +137,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
build_cli_annotations:
dependency: transitive
description:
name: build_cli_annotations
sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
url: "https://pub.dev"
source: hosted
version: "2.1.0"
canonical_json: canonical_json:
dependency: transitive dependency: transitive
description: description:
@ -608,14 +616,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
flutter_olm:
dependency: "direct main"
description:
name: flutter_olm
sha256: "5e6211af8cba1abf7d1f92e543f6d573dfe6017fe4742e0d04ba84beab47f940"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_openssl_crypto: flutter_openssl_crypto:
dependency: "direct main" dependency: "direct main"
description: description:
@ -632,6 +632,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.28" version: "2.0.28"
flutter_rust_bridge:
dependency: "direct main"
description:
name: flutter_rust_bridge
sha256: b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb
url: "https://pub.dev"
source: hosted
version: "2.10.0"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -702,6 +710,14 @@ packages:
url: "https://github.com/famedly/flutter_typeahead.git" url: "https://github.com/famedly/flutter_typeahead.git"
source: git source: git
version: "5.2.0" version: "5.2.0"
flutter_vodozemac:
dependency: "direct main"
description:
name: flutter_vodozemac
sha256: "2405ca121b84d1cd83200a14021022e1691b123a23bcefc36adc7740cefbc1f9"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
flutter_web_auth_2: flutter_web_auth_2:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1153,10 +1169,11 @@ packages:
matrix: matrix:
dependency: "direct main" dependency: "direct main"
description: description:
path: "../matrix-dart-sdk" name: matrix
relative: true sha256: "127cc89a030e1555fc02c3434f443feca942320af6248122b36c56448e59fb19"
source: path url: "https://pub.dev"
version: "0.40.0" source: hosted
version: "1.0.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -1213,14 +1230,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
olm:
dependency: transitive
description:
name: olm
sha256: "6a3fe1e5170b954dd9e4ba3b27513e6aa9b7591eb7bb0d7f6f32140b7f140c6f"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
opus_caf_converter_dart: opus_caf_converter_dart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -2194,6 +2203,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.0.0" version: "15.0.0"
vodozemac:
dependency: "direct main"
description:
name: vodozemac
sha256: dba14017e042748fb22d270e8ab1d3e46965b89788dd3857dba938ec07571968
url: "https://pub.dev"
source: hosted
version: "0.2.0"
wakelock_plus: wakelock_plus:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -38,14 +38,15 @@ dependencies:
sdk: flutter sdk: flutter
flutter_map: ^6.1.0 flutter_map: ^6.1.0
flutter_new_badger: ^1.1.1 flutter_new_badger: ^1.1.1
flutter_olm: 2.0.0
flutter_openssl_crypto: ^0.5.0 flutter_openssl_crypto: ^0.5.0
flutter_rust_bridge: ^2.10.0
flutter_secure_storage: ^9.2.2 flutter_secure_storage: ^9.2.2
flutter_shortcuts_new: ^2.0.0 flutter_shortcuts_new: ^2.0.0
flutter_typeahead: ## Custom fork from flutter_typeahead since the package is not maintain well. flutter_typeahead: ## Custom fork from flutter_typeahead since the package is not maintain well.
git: git:
url: https://github.com/famedly/flutter_typeahead.git url: https://github.com/famedly/flutter_typeahead.git
ref: main ref: main
flutter_vodozemac: ^0.2.2
flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379 flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379
flutter_webrtc: ^0.12.9 flutter_webrtc: ^0.12.9
geolocator: ^13.0.1 geolocator: ^13.0.1
@ -62,8 +63,7 @@ dependencies:
latlong2: ^0.9.1 latlong2: ^0.9.1
linkify: ^5.0.0 linkify: ^5.0.0
material: ^1.0.0+2 material: ^1.0.0+2
matrix: matrix: ^1.0.0
path: ../matrix-dart-sdk
mime: ^1.0.6 mime: ^1.0.6
native_imaging: ^0.2.0 native_imaging: ^0.2.0
opus_caf_converter_dart: ^1.0.1 opus_caf_converter_dart: ^1.0.1
@ -93,6 +93,7 @@ dependencies:
url_launcher: ^6.2.5 url_launcher: ^6.2.5
video_compress: ^3.1.4 video_compress: ^3.1.4
video_player: ^2.9.2 video_player: ^2.9.2
vodozemac: ^0.2.0
wakelock_plus: ^1.2.2 wakelock_plus: ^1.2.2
webrtc_interface: ^1.0.13 webrtc_interface: ^1.0.13

View File

@ -21,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_vodozemac
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)