diff --git a/lib/chat/authentication/chat_login_page.dart b/lib/chat/authentication/chat_login_page.dart index be200f2..0a3f1b3 100644 --- a/lib/chat/authentication/chat_login_page.dart +++ b/lib/chat/authentication/chat_login_page.dart @@ -50,7 +50,7 @@ class _ChatLoginPageState extends State { homeServer: _homeServerController.text.trim(), username: _usernameController.text, password: _passwordController.text, - onSuccess: () async => Navigator.of(context).pushAndRemoveUntil( + onSuccess: () => Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (_) => const ChatMasterDetailPage(), ), diff --git a/lib/chat/bootstrap/bootstrap_model.dart b/lib/chat/bootstrap/bootstrap_model.dart index cecc98e..1e89e2f 100644 --- a/lib/chat/bootstrap/bootstrap_model.dart +++ b/lib/chat/bootstrap/bootstrap_model.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -18,11 +17,11 @@ class BootstrapModel extends SafeChangeNotifier { final Client _client; final FlutterSecureStorage _secureStorage; - Future isBootrapNeeded() async => - _client.isUnknownSession && _client.encryption!.crossSigning.enabled; + Future checkBootstrap() async { + if (!_client.encryptionEnabled) { + return true; + } - Future isBootrapNeededFull() async { - if (!_client.encryptionEnabled) return true; await _client.accountDataLoading; await _client.userDeviceKeysLoading; if (_client.prevBatch == null) { @@ -112,12 +111,10 @@ class BootstrapModel extends SafeChangeNotifier { _bootstrap = _client.encryption?.bootstrap(onUpdate: (v) => _setBootsTrap(v)); final theKey = await _loadKeyFromSecureStorage(); - if (key == null) { - notifyListeners(); - return; + if (key != null) { + _key = theKey; } - _key = theKey; notifyListeners(); } @@ -131,8 +128,6 @@ class BootstrapModel extends SafeChangeNotifier { } } - bool get supportsSecureStorage => !kIsWeb; - String getSecureStorageLocalizedName(AppLocalizations l10n) { if (Platform.isAndroid) { return l10n.storeInAndroidKeystore; diff --git a/lib/chat/bootstrap/view/bootstrap_page.dart b/lib/chat/bootstrap/view/bootstrap_page.dart index 70e7eb4..a6cce63 100644 --- a/lib/chat/bootstrap/view/bootstrap_page.dart +++ b/lib/chat/bootstrap/view/bootstrap_page.dart @@ -17,9 +17,7 @@ import '../bootstrap_model.dart'; import 'key_verification_dialog.dart'; class BootstrapPage extends StatelessWidget with WatchItMixin { - const BootstrapPage({ - super.key, - }); + const BootstrapPage({super.key}); @override Widget build(BuildContext context) { @@ -46,7 +44,9 @@ class BootstrapPage extends StatelessWidget with WatchItMixin { if (key != null && recoveryKeyStored == false) { return Scaffold( - appBar: AppBar( + appBar: YaruWindowTitleBar( + border: BorderSide.none, + backgroundColor: Colors.transparent, centerTitle: true, leading: IconButton( icon: const Icon(YaruIcons.window_close), @@ -87,15 +87,14 @@ class BootstrapPage extends StatelessWidget with WatchItMixin { ), ), const SizedBox(height: 16), - if (model.supportsSecureStorage) - YaruCheckboxListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), - value: storeInSecureStorage, - onChanged: (v) => model.setStoreInSecureStorage(v ?? false), - title: - Text(model.getSecureStorageLocalizedName(context.l10n)), - subtitle: Text(l10n.storeInSecureStorageDescription), - ), + YaruCheckboxListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), + value: storeInSecureStorage, + onChanged: (v) => model.setStoreInSecureStorage(v ?? false), + title: + Text(model.getSecureStorageLocalizedName(context.l10n)), + subtitle: Text(l10n.storeInSecureStorageDescription), + ), const SizedBox(height: 16), YaruCheckboxListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -213,8 +212,10 @@ class BootstrapPage extends StatelessWidget with WatchItMixin { } return Scaffold( - appBar: YaruDialogTitleBar( + appBar: YaruWindowTitleBar( title: Text(titleText), + border: BorderSide.none, + backgroundColor: Colors.transparent, ), body: Center( child: Column( @@ -230,34 +231,6 @@ class BootstrapPage extends StatelessWidget with WatchItMixin { } } -Future showAdaptiveBottomSheet({ - required BuildContext context, - required Widget Function(BuildContext) builder, - bool isDismissible = true, - bool isScrollControlled = true, - double maxHeight = 512, - bool useRootNavigator = true, -}) => - showModalBottomSheet( - context: context, - builder: builder, - // this sadly is ugly on desktops but otherwise breaks `.of(context)` calls - useRootNavigator: useRootNavigator, - isDismissible: isDismissible, - isScrollControlled: isScrollControlled, - constraints: BoxConstraints( - maxHeight: maxHeight, - maxWidth: 400, - ), - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), - ), - ); - class OpenExistingSSSSPage extends StatefulWidget with WatchItStatefulWidgetMixin { const OpenExistingSSSSPage({super.key}); @@ -289,6 +262,10 @@ class _OpenExistingSSSSPageState extends State { watchPropertyValue((BootstrapModel m) => m.recoveryKeyInputError); return Scaffold( + appBar: const YaruWindowTitleBar( + border: BorderSide.none, + backgroundColor: Colors.transparent, + ), body: Center( child: SizedBox( width: 400, diff --git a/lib/chat/bootstrap/view/key_verification_dialog.dart b/lib/chat/bootstrap/view/key_verification_dialog.dart index 2681e81..b3835dd 100644 --- a/lib/chat/bootstrap/view/key_verification_dialog.dart +++ b/lib/chat/bootstrap/view/key_verification_dialog.dart @@ -7,12 +7,15 @@ import 'package:flutter/services.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; +import 'package:yaru/yaru.dart'; import '../../../common/view/build_context_x.dart'; import '../../../l10n/l10n.dart'; import '../../view/chat_avatar.dart'; +import '../../view/chat_master/chat_master_detail_page.dart'; -// TODO: code by fluffy-chat, replace +// Credit: this code has been initially copied from https://github.com/krille-chan/fluffychat +// Thank you @krille-chan class KeyVerificationDialog extends StatefulWidget { Future show(BuildContext context) => showAdaptiveDialog( context: context, @@ -21,14 +24,10 @@ class KeyVerificationDialog extends StatefulWidget { ); final KeyVerification request; - final Function()? onCancel; - final Function()? onDone; const KeyVerificationDialog({ super.key, required this.request, - this.onCancel, - this.onDone, }); @override @@ -188,7 +187,7 @@ class KeyVerificationPageState extends State { ); buttons.add( TextButton.icon( - icon: const Icon(Icons.close), + icon: const Icon(YaruIcons.window_close), style: TextButton.styleFrom(foregroundColor: Colors.red), label: Text(l10n.reject), onPressed: () => widget.request.rejectVerification().then((_) { @@ -200,7 +199,7 @@ class KeyVerificationPageState extends State { ); buttons.add( TextButton.icon( - icon: const Icon(Icons.check), + icon: const Icon(YaruIcons.checkmark), label: Text(l10n.accept), onPressed: () => widget.request.acceptVerification(), ), @@ -233,7 +232,7 @@ class KeyVerificationPageState extends State { ); buttons.add( TextButton.icon( - icon: const Icon(Icons.close), + icon: const Icon(YaruIcons.window_close), label: Text(l10n.cancel), onPressed: () => widget.request.cancel(), ), @@ -273,7 +272,7 @@ class KeyVerificationPageState extends State { ); buttons.add( TextButton.icon( - icon: const Icon(Icons.close), + icon: const Icon(YaruIcons.window_close), style: TextButton.styleFrom( foregroundColor: Colors.red, ), @@ -283,7 +282,7 @@ class KeyVerificationPageState extends State { ); buttons.add( TextButton.icon( - icon: const Icon(Icons.check_outlined), + icon: const Icon(YaruIcons.checkmark), label: Text(l10n.theyMatch), onPressed: () => widget.request.acceptSas(), ), @@ -308,7 +307,7 @@ class KeyVerificationPageState extends State { mainAxisSize: MainAxisSize.min, children: [ const Icon( - Icons.check_circle_outlined, + YaruIcons.ok, color: Colors.green, size: 128.0, ), @@ -325,12 +324,17 @@ class KeyVerificationPageState extends State { l10n.close, ), onPressed: () { - widget.onDone?.call(); if (context.mounted) { Navigator.of( context, rootNavigator: false, ).pop(); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (_) => const ChatMasterDetailPage(), + ), + (route) => false, + ); } }, ), @@ -340,7 +344,7 @@ class KeyVerificationPageState extends State { body = Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.cancel, color: Colors.red, size: 128.0), + const Icon(YaruIcons.edit_clear, color: Colors.red, size: 128.0), const SizedBox(height: 16), Text( 'Error ${widget.request.canceledCode}: ${widget.request.canceledReason}', diff --git a/lib/chat/chat_download_service.dart b/lib/chat/chat_download_service.dart index bb989b4..5d60583 100644 --- a/lib/chat/chat_download_service.dart +++ b/lib/chat/chat_download_service.dart @@ -4,8 +4,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; - -import '../app_config.dart'; +import 'package:yaru/yaru.dart'; class ChatDownloadService { ChatDownloadService({ @@ -34,7 +33,7 @@ class ChatDownloadService { } MatrixFile? file; String? path; - if (isMobilePlatform) { + if (isMobile) { file = await event.downloadAndDecryptAttachment(); path = await FilePicker.platform.saveFile( fileName: file.name, diff --git a/lib/chat/draft_model.dart b/lib/chat/draft_model.dart index 8ab5836..e31a171 100644 --- a/lib/chat/draft_model.dart +++ b/lib/chat/draft_model.dart @@ -6,8 +6,8 @@ import 'package:matrix/matrix.dart'; import 'package:mime/mime.dart'; import 'package:safe_change_notifier/safe_change_notifier.dart'; import 'package:video_compress/video_compress.dart'; +import 'package:yaru/yaru.dart'; -import '../app_config.dart'; import '../common/logging.dart'; import 'local_image_service.dart'; @@ -79,11 +79,12 @@ class DraftModel extends SafeChangeNotifier { } } - final draft = '${getDraft(room.id)}'; - removeDraft(room.id); - if (draft.isNotEmpty) { + if (getDraft(room.id)?.isNotEmpty == true) { + final draft = '${getDraft(room.id)}'; + removeDraft(room.id); + String? eventId; try { - await room.sendTextEvent( + eventId = await room.sendTextEvent( draft.trim(), inReplyTo: replyEvent, editEventId: _editEvents[room.id]?.eventId, @@ -92,6 +93,9 @@ class DraftModel extends SafeChangeNotifier { onFail(e.toString()); printMessageInDebugMode(e, s); } + if (eventId == null) { + setDraft(roomId: room.id, draft: draft, notify: true); + } } _sending = false; @@ -237,7 +241,7 @@ class DraftModel extends SafeChangeNotifier { Future resizeVideo(XFile xFile) async { MediaInfo? mediaInfo; try { - if (isMobilePlatform) { + if (isMobile) { // will throw an error e.g. on Android SDK < 18 mediaInfo = await VideoCompress.compressVideo(xFile.path); } @@ -258,7 +262,7 @@ class DraftModel extends SafeChangeNotifier { // final Map _fileMap = {}; Future getVideoThumbnail(XFile xFile) async { - if (!isMobilePlatform) return null; + if (!isMobile) return null; try { final bytes = await VideoCompress.getByteThumbnail(xFile.path); diff --git a/lib/chat/view/chat_master/chat_master_detail_page.dart b/lib/chat/view/chat_master/chat_master_detail_page.dart index 21bad7e..93f9a4b 100644 --- a/lib/chat/view/chat_master/chat_master_detail_page.dart +++ b/lib/chat/view/chat_master/chat_master_detail_page.dart @@ -28,24 +28,25 @@ class _ChatMasterDetailPageState extends State { @override void initState() { super.initState(); - di().isBootrapNeeded().then( - (isNeeded) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final bootstrapModel = di(); + bootstrapModel.checkBootstrap().then((isNeeded) { if (isNeeded) { - di().startBootstrap(wipe: false).then((value) { - if (mounted) { - showDialog( - context: context, - builder: (context) => const SizedBox( - height: 500, - width: 400, - child: BootstrapPage(), - ), - ); - } - }); + bootstrapModel.startBootstrap(wipe: false).then( + (_) { + if (mounted) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (_) => const BootstrapPage(), + ), + (route) => false, + ); + } + }, + ); } - }, - ); + }); + }); } @override diff --git a/lib/chat/view/events/chat_html_message.dart b/lib/chat/view/events/chat_html_message.dart index 2faf973..a1cced9 100644 --- a/lib/chat/view/events/chat_html_message.dart +++ b/lib/chat/view/events/chat_html_message.dart @@ -15,8 +15,7 @@ import '../../../common/view/confirm.dart'; import '../../../l10n/l10n.dart'; import '../mxc_image.dart'; -// Credit: this code has been copied and from https://github.com/krille-chan/fluffychat -// and then modified +// Credit: this code has been initially copied from https://github.com/krille-chan/fluffychat // Thank you @krille-chan class HtmlMessage extends StatelessWidget { final String html; diff --git a/lib/chat/view/events/chat_message_reactions.dart b/lib/chat/view/events/chat_message_reactions.dart index 9ae4a51..2bfe6bc 100644 --- a/lib/chat/view/events/chat_message_reactions.dart +++ b/lib/chat/view/events/chat_message_reactions.dart @@ -16,6 +16,8 @@ import '../../chat_model.dart'; import '../chat_avatar.dart'; import '../mxc_image.dart'; +// Credit: this code has been initially copied from https://github.com/krille-chan/fluffychat +// Thank you @krille-chan class ChatMessageReactions extends StatelessWidget { final Event event; final Timeline timeline; diff --git a/lib/register.dart b/lib/register.dart index d1b399f..9023118 100644 --- a/lib/register.dart +++ b/lib/register.dart @@ -131,32 +131,24 @@ extension _ClientX on Client { } final client = Client( kAppId, - nativeImplementations: kIsWeb - ? const NativeImplementationsDummy() - : NativeImplementationsIsolate(compute), + nativeImplementations: NativeImplementationsIsolate(compute), verificationMethods: { KeyVerificationMethod.numbers, - if (kIsWeb || Platform.isAndroid || Platform.isIOS || Platform.isLinux) - KeyVerificationMethod.emoji, + if (Platform.isAndroid || Platform.isLinux) KeyVerificationMethod.emoji, + }, + databaseBuilder: (_) async { + final dir = await getApplicationSupportDirectory(); + final db = MatrixSdkDatabase( + kAppId, + database: + await sqlite.openDatabase(p.join(dir.path, 'database.sqlite')), + ); + await db.open(); + return db; }, - databaseBuilder: kIsWeb - ? null - : (_) async { - final dir = await getApplicationSupportDirectory(); - final db = MatrixSdkDatabase( - kAppId, - database: await sqlite - .openDatabase(p.join(dir.path, 'database.sqlite')), - ); - await db.open(); - return db; - }, ); // This reads potential credentials that might exist from previous sessions. - await client.init(waitForFirstSync: client.isLogged()); - await client.firstSyncReceived; - await client.roomsLoading; - + await client.init(); return client; } }