From ede88d3c56a658165d13a25d1a92cc5d02ff9d7c Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Thu, 25 Apr 2024 10:17:23 +0200 Subject: [PATCH 01/18] TW-1699: app does start on linux --- lib/config/go_routes/go_router.dart | 2 +- lib/pages/bootstrap/bootstrap_dialog.dart | 2 +- lib/pages/chat/events/message_content.dart | 1 + lib/pages/dialer/dialer.dart | 5 +++- .../mixins/connect_page_mixin.dart | 9 ++++++- lib/utils/client_manager.dart | 3 ++- lib/utils/voip/user_media_manager.dart | 2 +- pubspec.lock | 25 ++++++++++--------- pubspec.yaml | 9 ++++++- 9 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index d585d5dd32..c030f8f853 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -78,7 +78,7 @@ abstract class AppRoutes { path: '/home', pageBuilder: (context, state) => defaultPageBuilder( context, - PlatformInfos.isMobile + PlatformInfos.isMobile || PlatformInfos.isLinux ? const TwakeWelcome() : AutoHomeserverPicker( loggedOut: state.extra is bool ? state.extra as bool? : null, diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index bca0fa0442..b948ae7a16 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -67,7 +67,7 @@ class BootstrapDialogState extends State { if (PlatformInfos.isAndroid) { return L10n.of(context)!.storeInAndroidKeystore; } - if (PlatformInfos.isIOS || PlatformInfos.isMacOS) { + if (PlatformInfos.isIOS || PlatformInfos.isMacOS || PlatformInfos.isLinux) { return L10n.of(context)!.storeInAppleKeyChain; } return L10n.of(context)!.storeSecurlyOnThisDevice; diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 9e367c14d8..4f5495a81b 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -76,6 +76,7 @@ class MessageContent extends StatelessWidget if (PlatformInfos.isMobile || PlatformInfos.isMacOS || PlatformInfos.isWeb + // TODO: test this // Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3 // is fixed // || PlatformInfos.isLinux diff --git a/lib/pages/dialer/dialer.dart b/lib/pages/dialer/dialer.dart index c1a5cfd739..a306bbda79 100644 --- a/lib/pages/dialer/dialer.dart +++ b/lib/pages/dialer/dialer.dart @@ -186,7 +186,10 @@ class MyCallingPage extends State { void _playCallSound() async { const path = 'assets/sounds/call.ogg'; - if (kIsWeb || PlatformInfos.isMobile || PlatformInfos.isMacOS) { + if (kIsWeb || + PlatformInfos.isMobile || + PlatformInfos.isMacOS || + PlatformInfos.isLinux) { final player = AudioPlayer(); await player.setAsset(path); player.play(); diff --git a/lib/presentation/mixins/connect_page_mixin.dart b/lib/presentation/mixins/connect_page_mixin.dart index 9f5ec79a41..e42dccd2bf 100644 --- a/lib/presentation/mixins/connect_page_mixin.dart +++ b/lib/presentation/mixins/connect_page_mixin.dart @@ -37,7 +37,8 @@ mixin ConnectPageMixin { bool supportsSso(BuildContext context) => (PlatformInfos.isMobile || PlatformInfos.isWeb || - PlatformInfos.isMacOS) && + PlatformInfos.isMacOS || + PlatformInfos.isLinux) && supportsFlow(context: context, flowType: 'm.login.sso'); bool supportsLogin(BuildContext context) => @@ -58,6 +59,11 @@ mixin ConnectPageMixin { AppConfig.homeserver.isNotEmpty; String _getRedirectUrlScheme(String redirectUrl) { + // Remove when package limitation will be fixed + // https://pub.dev/packages/flutter_web_auth_2#windows-and-linux + if (PlatformInfos.isLinux || PlatformInfos.isWindows) { + return "http://localhost:60665"; + } return Uri.parse(redirectUrl).scheme; } @@ -106,6 +112,7 @@ mixin ConnectPageMixin { redirectUrl: redirectUrl, ); final urlScheme = _getRedirectUrlScheme(redirectUrl); + print("tez: urlScheme = $urlScheme"); return await FlutterWebAuth2.authenticate( url: url, callbackUrlScheme: urlScheme, diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 7b9cf5418c..17cfa360e4 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -107,7 +107,8 @@ abstract class ClientManager { AuthenticationTypes.password, if (PlatformInfos.isMobile || PlatformInfos.isWeb || - PlatformInfos.isMacOS) + PlatformInfos.isMacOS || + PlatformInfos.isLinux) AuthenticationTypes.sso, }, nativeImplementations: nativeImplementations, diff --git a/lib/utils/voip/user_media_manager.dart b/lib/utils/voip/user_media_manager.dart index 874da93ee1..f6b747efe5 100644 --- a/lib/utils/voip/user_media_manager.dart +++ b/lib/utils/voip/user_media_manager.dart @@ -19,7 +19,7 @@ class UserMediaManager { Future startRingingTone() async { if (PlatformInfos.isMobile) { await FlutterRingtonePlayer.playRingtone(volume: 80); - } else if ((kIsWeb || PlatformInfos.isMacOS) && + } else if ((kIsWeb || PlatformInfos.isMacOS || PlatformInfos.isLinux) && _assetsAudioPlayer != null) { const path = 'assets/sounds/phone.ogg'; final player = _assetsAudioPlayer = AudioPlayer(); diff --git a/pubspec.lock b/pubspec.lock index 6d22a13ddc..f8977ed4cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1022,26 +1022,27 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: f2afec1f1762c040a349ea2a588e32f442da5d0db3494a52a929a97c9e550bc5 + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "9.0.0" flutter_secure_storage_linux: - dependency: transitive + dependency: "direct overridden" description: - name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" - url: "https://pub.dev" - source: hosted + path: flutter_secure_storage_linux + ref: develop + resolved-ref: "27d3e2e69123f0c712919ad392e15830741e4383" + url: "https://github.com/tomekit/flutter_secure_storage.git" + source: git version: "1.2.0" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: ff0768a6700ea1d9620e03518e2e25eac86a8bd07ca3556e9617bfa5ace4bd00 + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -3064,13 +3065,13 @@ packages: source: hosted version: "1.1.0" web: - dependency: transitive + dependency: "direct overridden" description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 49176b0ff7..331a9aac58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,7 +89,7 @@ dependencies: flutter_olm: ^1.2.0 flutter_openssl_crypto: ^0.1.0 flutter_ringtone_player: ^3.1.1 - flutter_secure_storage: ^7.0.1 + flutter_secure_storage: ^9.0.0 flutter_svg: ^0.22.0 flutter_typeahead: ^5.1.0 flutter_web_auth_2: ^3.1.1 @@ -245,6 +245,12 @@ dependency_overrides: git: url: https://gitlab.com/TheOneWithTheBraid/flutter_secure_storage_windows.git ref: main + # https://github.com/mogol/flutter_secure_storage/issues/616 + flutter_secure_storage_linux: + git: + url: https://github.com/tomekit/flutter_secure_storage.git + ref: develop + path: flutter_secure_storage_linux geolocator_android: hosted: name: geolocator_android @@ -269,6 +275,7 @@ dependency_overrides: git: url: https://github.com/linagora/matrix_link_text.git ref: twake-supported + web: ^0.5.0 cider: link_template: From bf0590d8a06ea8e6a797a2c40316015bf332c25a Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Fri, 26 Apr 2024 07:47:13 +0200 Subject: [PATCH 02/18] fixup! TW-1699: app does start on linux --- lib/presentation/mixins/connect_page_mixin.dart | 13 +++++++------ lib/utils/platform_infos.dart | 2 ++ pubspec.lock | 6 +++--- pubspec.yaml | 1 - 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/presentation/mixins/connect_page_mixin.dart b/lib/presentation/mixins/connect_page_mixin.dart index e42dccd2bf..197ed9447d 100644 --- a/lib/presentation/mixins/connect_page_mixin.dart +++ b/lib/presentation/mixins/connect_page_mixin.dart @@ -24,6 +24,8 @@ mixin ConnectPageMixin { static const redirectPublicPlatformOnWeb = 'post_login_redirect_url'; + static const linowsRedirectUrl = 'http://localhost:60665'; + bool supportsFlow({ required BuildContext context, required String flowType, @@ -59,10 +61,8 @@ mixin ConnectPageMixin { AppConfig.homeserver.isNotEmpty; String _getRedirectUrlScheme(String redirectUrl) { - // Remove when package limitation will be fixed - // https://pub.dev/packages/flutter_web_auth_2#windows-and-linux - if (PlatformInfos.isLinux || PlatformInfos.isWindows) { - return "http://localhost:60665"; + if (PlatformInfos.isLinows) { + return linowsRedirectUrl; } return Uri.parse(redirectUrl).scheme; } @@ -112,8 +112,8 @@ mixin ConnectPageMixin { redirectUrl: redirectUrl, ); final urlScheme = _getRedirectUrlScheme(redirectUrl); - print("tez: urlScheme = $urlScheme"); - return await FlutterWebAuth2.authenticate( + + return FlutterWebAuth2.authenticate( url: url, callbackUrlScheme: urlScheme, options: const FlutterWebAuth2Options( @@ -222,6 +222,7 @@ mixin ConnectPageMixin { } String _generateRedirectUrl(String homeserver) { + if (PlatformInfos.isLinows) return linowsRedirectUrl; if (kIsWeb) { String? homeserverParam = ''; if (homeserver.isNotEmpty) { diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index d6ee76d22f..b57d2874f2 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -32,6 +32,8 @@ abstract class PlatformInfos { static bool get isDesktop => isLinux || isWindows || isMacOS; + static bool get isLinows => isLinux || isWindows; + static bool get usesTouchscreen => !isMobile; static bool get platformCanRecord => (isMobile || isMacOS); diff --git a/pubspec.lock b/pubspec.lock index f8977ed4cc..9c80dfc886 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -3065,13 +3065,13 @@ packages: source: hosted version: "1.1.0" web: - dependency: "direct overridden" + dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.3.0" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 331a9aac58..f621525be3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -275,7 +275,6 @@ dependency_overrides: git: url: https://github.com/linagora/matrix_link_text.git ref: twake-supported - web: ^0.5.0 cider: link_template: From 04216db72f4b6308e1b96bd42c6f6fa78d862607 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Mon, 29 Apr 2024 13:39:23 +0200 Subject: [PATCH 03/18] TW-1699: PlatformInfos is desktop enabled --- lib/pages/chat/chat_event_list.dart | 2 +- .../events/message/multi_platform_message_container.dart | 2 +- lib/pages/chat/events/message_content.dart | 7 +++---- .../chat_adaptive_scaffold_builder.dart | 2 +- lib/pages/chat_list/chat_list_header.dart | 2 +- lib/pages/chat_search/chat_search_view.dart | 2 +- lib/pages/image_viewer/image_viewer.dart | 2 +- lib/pages/image_viewer/image_viewer_style.dart | 4 ++-- lib/pages/image_viewer/image_viewer_view.dart | 4 ++-- lib/pages/image_viewer/media_viewer_app_bar_view.dart | 2 +- lib/pages/new_group/widget/selected_participants_list.dart | 2 +- .../settings_emotes/settings_emotes_view.dart | 3 +-- lib/presentation/mixins/connect_page_mixin.dart | 4 ++-- lib/utils/platform_infos.dart | 4 +++- .../layouts/adaptive_layout/adaptive_scaffold_appbar.dart | 2 +- lib/widgets/video_viewer_style.dart | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 1514143825..275d77602e 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -241,7 +241,7 @@ class SelectionTextContainer extends StatelessWidget { @override Widget build(BuildContext context) { - if (!PlatformInfos.isWeb) { + if (PlatformInfos.isMobile) { return child; } diff --git a/lib/pages/chat/events/message/multi_platform_message_container.dart b/lib/pages/chat/events/message/multi_platform_message_container.dart index d6a03bcd5d..fbe6b75919 100644 --- a/lib/pages/chat/events/message/multi_platform_message_container.dart +++ b/lib/pages/chat/events/message/multi_platform_message_container.dart @@ -18,7 +18,7 @@ class MultiPlatformsMessageContainer extends StatelessWidget { @override Widget build(BuildContext context) { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { return MouseRegion( child: child, onHover: (event) { diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 4f5495a81b..0d56245b68 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -76,7 +76,6 @@ class MessageContent extends StatelessWidget if (PlatformInfos.isMobile || PlatformInfos.isMacOS || PlatformInfos.isWeb - // TODO: test this // Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3 // is fixed // || PlatformInfos.isLinux @@ -102,7 +101,7 @@ class MessageContent extends StatelessWidget return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (!PlatformInfos.isWeb) ...[ + if (!PlatformInfos.isWebOrDesktop) ...[ MessageDownloadContent( event, ), @@ -127,7 +126,7 @@ class MessageContent extends StatelessWidget return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (!PlatformInfos.isWeb) ...[ + if (!PlatformInfos.isWebOrDesktop) ...[ MessageDownloadContent( event, ), @@ -318,7 +317,7 @@ class _MessageImageBuilder extends StatelessWidget { return matrixFile != null && matrixFile.filePath != null && matrixFile is MatrixImageFile && - !PlatformInfos.isWeb; + !PlatformInfos.isWebOrDesktop; } } diff --git a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart index 324db4a8e4..cc4aeccb3b 100644 --- a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart +++ b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart @@ -87,7 +87,7 @@ class ChatAdaptiveScaffoldBuilderController builder: (_) => Stack( children: [ body!, - if (rightColumnType != null && PlatformInfos.isWeb) + if (rightColumnType != null && PlatformInfos.isWebOrDesktop) widget.rightBuilder( this, isInStack: true, diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index e141149380..3dca912866 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -28,7 +28,7 @@ class ChatListHeader extends StatelessWidget { Container( height: ChatListHeaderStyle.searchBarContainerHeight, padding: ChatListHeaderStyle.searchInputPadding, - child: PlatformInfos.isWeb + child: PlatformInfos.isWebOrDesktop ? _normalModeWidgetWeb(context) : _normalModeWidgetsMobile(context), ), diff --git a/lib/pages/chat_search/chat_search_view.dart b/lib/pages/chat_search/chat_search_view.dart index 61ea3c69fb..65851003ff 100644 --- a/lib/pages/chat_search/chat_search_view.dart +++ b/lib/pages/chat_search/chat_search_view.dart @@ -286,7 +286,7 @@ class _MessageContent extends StatelessWidget { Widget build(BuildContext context) { switch (event.messageType) { case MessageTypes.File: - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { return MessageDownloadContentWeb(event, highlightText: searchWord); } else { return MessageDownloadContent(event, highlightText: searchWord); diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index eaa40b8a17..0dececb5a4 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -45,7 +45,7 @@ class ImageViewerController extends State { @override void initState() { super.initState(); - if (!PlatformInfos.isWeb && widget.event != null) { + if (!PlatformInfos.isWebOrDesktop && widget.event != null) { handleDownloadFile(widget.event!); } } diff --git a/lib/pages/image_viewer/image_viewer_style.dart b/lib/pages/image_viewer/image_viewer_style.dart index 3d88427473..ecc0f9b314 100644 --- a/lib/pages/image_viewer/image_viewer_style.dart +++ b/lib/pages/image_viewer/image_viewer_style.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; class ImageViewerStyle { static const double minScaleInteractiveViewer = 1.0; static const double maxScaleInteractiveViewer = 10.0; - static double? appBarHeight = PlatformInfos.isWeb ? 56 : null; + static double? appBarHeight = PlatformInfos.isWebOrDesktop ? 56 : null; static EdgeInsetsGeometry paddingTopAppBar = EdgeInsetsDirectional.only( - top: PlatformInfos.isWeb ? 0 : 56, + top: PlatformInfos.isWebOrDesktop ? 0 : 56, ); } diff --git a/lib/pages/image_viewer/image_viewer_view.dart b/lib/pages/image_viewer/image_viewer_view.dart index 58742ba159..b4c0445ff4 100644 --- a/lib/pages/image_viewer/image_viewer_view.dart +++ b/lib/pages/image_viewer/image_viewer_view.dart @@ -69,7 +69,7 @@ class ImageViewerView extends StatelessWidget { backgroundColor: Colors.black, body: GestureDetector( onTap: () { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { Navigator.of(context).pop(); } else { controller.showAppbarPreview.toggle(); @@ -101,7 +101,7 @@ class _ImageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { return FutureBuilder( future: event.downloadAndDecryptAttachment( getThumbnail: true, diff --git a/lib/pages/image_viewer/media_viewer_app_bar_view.dart b/lib/pages/image_viewer/media_viewer_app_bar_view.dart index b8a724bc37..aca9ace062 100644 --- a/lib/pages/image_viewer/media_viewer_app_bar_view.dart +++ b/lib/pages/image_viewer/media_viewer_app_bar_view.dart @@ -92,7 +92,7 @@ class MediaViewerAppbarView extends StatelessWidget { menuChildren: [ ContextMenuItemImageViewer( icon: Icons.file_download_outlined, - title: PlatformInfos.isWeb + title: PlatformInfos.isWebOrDesktop ? L10n.of(context)!.saveFile : L10n.of(context)!.saveToGallery, onTap: () { diff --git a/lib/pages/new_group/widget/selected_participants_list.dart b/lib/pages/new_group/widget/selected_participants_list.dart index 6ded008627..979c62f2c1 100644 --- a/lib/pages/new_group/widget/selected_participants_list.dart +++ b/lib/pages/new_group/widget/selected_participants_list.dart @@ -45,7 +45,7 @@ class _SelectedParticipantsListState extends State { padding: SelectedParticipantsListStyle.paddingAll, child: Wrap( spacing: 8.0, - runSpacing: PlatformInfos.isWeb ? 4.0 : 0.0, + runSpacing: PlatformInfos.isWebOrDesktop ? 4.0 : 0.0, children: contactsNotifier.contactsList.map((contact) { return InputChip( shape: RoundedRectangleBorder( diff --git a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart index fb43918231..644f05692c 100644 --- a/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_dashboard/settings_emotes/settings_emotes_view.dart @@ -120,8 +120,7 @@ class EmotesSettingsView extends StatelessWidget { final image = controller.pack!.images[imageCode]!; final textEditingController = TextEditingController(); textEditingController.text = imageCode; - final useShortCuts = - (PlatformInfos.isWeb || PlatformInfos.isDesktop); + final useShortCuts = PlatformInfos.isWebOrDesktop; return ListTile( leading: Container( width: 180.0, diff --git a/lib/presentation/mixins/connect_page_mixin.dart b/lib/presentation/mixins/connect_page_mixin.dart index 197ed9447d..ea93279ce9 100644 --- a/lib/presentation/mixins/connect_page_mixin.dart +++ b/lib/presentation/mixins/connect_page_mixin.dart @@ -61,7 +61,7 @@ mixin ConnectPageMixin { AppConfig.homeserver.isNotEmpty; String _getRedirectUrlScheme(String redirectUrl) { - if (PlatformInfos.isLinows) { + if (PlatformInfos.isLinuxOrWindows) { return linowsRedirectUrl; } return Uri.parse(redirectUrl).scheme; @@ -222,7 +222,7 @@ mixin ConnectPageMixin { } String _generateRedirectUrl(String homeserver) { - if (PlatformInfos.isLinows) return linowsRedirectUrl; + if (PlatformInfos.isLinuxOrWindows) return linowsRedirectUrl; if (kIsWeb) { String? homeserverParam = ''; if (homeserver.isNotEmpty) { diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index b57d2874f2..2f4ab9467d 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -32,7 +32,9 @@ abstract class PlatformInfos { static bool get isDesktop => isLinux || isWindows || isMacOS; - static bool get isLinows => isLinux || isWindows; + static bool get isLinuxOrWindows => isLinux || isWindows; + + static bool get isWebOrDesktop => isWeb || isDesktop; static bool get usesTouchscreen => !isMobile; diff --git a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart index 317052f76a..29448bd301 100644 --- a/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart +++ b/lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart @@ -33,7 +33,7 @@ class AdaptiveScaffoldAppBar extends StatelessWidget { children: [ const _LeadingAppBarWidget(), if (AppConfig.appGridDashboardAvailable && - PlatformInfos.isWeb) + PlatformInfos.isWebOrDesktop) const Expanded( child: AppGridDashboard(), ), diff --git a/lib/widgets/video_viewer_style.dart b/lib/widgets/video_viewer_style.dart index 07d9425fa3..7420582ae8 100644 --- a/lib/widgets/video_viewer_style.dart +++ b/lib/widgets/video_viewer_style.dart @@ -14,7 +14,7 @@ class VideoViewerStyle { bottom: 8.0 + MediaQuery.of(context).viewPadding.bottom, ); - static EdgeInsets backButtonMargin(context) => PlatformInfos.isWeb + static EdgeInsets backButtonMargin(context) => PlatformInfos.isWebOrDesktop ? const EdgeInsets.only(top: 8.0, left: 16.0) : EdgeInsets.only(top: MediaQuery.of(context).viewPadding.top); From 9beaebe84541ea1907283e2c7508106fd0aaead8 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Mon, 29 Apr 2024 18:15:31 +0200 Subject: [PATCH 04/18] TW-1699: download attachment on desktop done --- assets/l10n/intl_en.arb | 1 + assets/l10n/intl_fr.arb | 1 + lib/pages/chat/events/message_content.dart | 4 +- .../chat_adaptive_scaffold_builder.dart | 3 +- .../mixins/media_viewer_app_bar_mixin.dart | 2 +- .../download_manager/download_manager.dart | 6 +++ .../manager/storage_directory_manager.dart | 23 ++++++--- .../download_file_extension.dart | 15 +++++- .../event_extension.dart | 2 +- .../matrix_file_extension.dart | 12 ++++- .../mixins/download_file_on_mobile_mixin.dart | 3 ++ ...andle_download_and_preview_file_mixin.dart | 48 +++++++++++++++++-- pubspec.lock | 13 ++--- pubspec.yaml | 4 +- 14 files changed, 110 insertions(+), 27 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 9c8242dd2e..8b7c2796b9 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2792,6 +2792,7 @@ "downloadImageSuccess": "Image saved to Pictures", "@downloadImageSuccess": {}, "downloadImageError": "Error saving image", + "downloadFileError": "Error downloading file", "@downloadImageError": {}, "downloadFileInWeb": "File saved to {directory}", "@downloadFileInWeb": { diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index ab65dd7510..fc63e52540 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -2696,6 +2696,7 @@ "@acceptInvite": {}, "downloadImageError": "Erreur d'enregistrement de l'image", "@downloadImageError": {}, + "downloadFileError": "Erreur de téléchargement du fichier", "externalContactMessage": "Certains des utilisateurs que vous souhaitez ajouter ne figurent pas dans vos contacts. Voulez-vous les inviter ?", "@externalContactMessage": {}, "appLanguage": "Langue de l'application", diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 0d56245b68..a61e7ea654 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -101,7 +101,7 @@ class MessageContent extends StatelessWidget return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (!PlatformInfos.isWebOrDesktop) ...[ + if (PlatformInfos.isMobile) ...[ MessageDownloadContent( event, ), @@ -126,7 +126,7 @@ class MessageContent extends StatelessWidget return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (!PlatformInfos.isWebOrDesktop) ...[ + if (PlatformInfos.isMobile || PlatformInfos.isDesktop) ...[ MessageDownloadContent( event, ), diff --git a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart index cc4aeccb3b..0f69d4a881 100644 --- a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart +++ b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold_builder.dart @@ -87,7 +87,8 @@ class ChatAdaptiveScaffoldBuilderController builder: (_) => Stack( children: [ body!, - if (rightColumnType != null && PlatformInfos.isWebOrDesktop) + if (rightColumnType != null && + PlatformInfos.isWebOrDesktop) widget.rightBuilder( this, isInStack: true, diff --git a/lib/presentation/mixins/media_viewer_app_bar_mixin.dart b/lib/presentation/mixins/media_viewer_app_bar_mixin.dart index 9bd069b01f..9c0e9ed9c2 100644 --- a/lib/presentation/mixins/media_viewer_app_bar_mixin.dart +++ b/lib/presentation/mixins/media_viewer_app_bar_mixin.dart @@ -151,7 +151,7 @@ mixin MediaViewerAppBarMixin on SaveMediaToGalleryAndroidMixin { BuildContext context, Event? event, ) { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { event?.saveFile(context); } else { if (event != null) { diff --git a/lib/utils/manager/download_manager/download_manager.dart b/lib/utils/manager/download_manager/download_manager.dart index a283163d0a..6f666c8f72 100644 --- a/lib/utils/manager/download_manager/download_manager.dart +++ b/lib/utils/manager/download_manager/download_manager.dart @@ -98,6 +98,7 @@ class DownloadManager { required Event event, bool getThumbnail = false, bool isFirstPriority = false, + bool isTemporary = true, }) async { _initDownloadFileInfo(event); final streamController = _eventIdMapDownloadFileInfo[event.eventId] @@ -131,6 +132,7 @@ class DownloadManager { streamController: streamController, cancelToken: cancelToken, isFirstPriority: isFirstPriority, + isTemporary: isTemporary, ); } @@ -140,6 +142,7 @@ class DownloadManager { required StreamController> streamController, required CancelToken cancelToken, bool isFirstPriority = false, + bool isTemporary = true, }) { if (PlatformInfos.isWeb) { _addTaskToWorkerQueueWeb( @@ -157,6 +160,7 @@ class DownloadManager { streamController, cancelToken, isFirstPriority: isFirstPriority, + isTemporary: isTemporary, ); } @@ -166,6 +170,7 @@ class DownloadManager { StreamController> streamController, CancelToken cancelToken, { bool isFirstPriority = false, + bool isTemporary = true, }) { workingQueue.addTask( Task( @@ -176,6 +181,7 @@ class DownloadManager { getThumbnail: getThumbnail, downloadStreamController: streamController, cancelToken: cancelToken, + isTemporary: isTemporary, ); } catch (e) { Logs().e('DownloadManager::download(): $e'); diff --git a/lib/utils/manager/storage_directory_manager.dart b/lib/utils/manager/storage_directory_manager.dart index a45902f8f5..24ff934e02 100644 --- a/lib/utils/manager/storage_directory_manager.dart +++ b/lib/utils/manager/storage_directory_manager.dart @@ -13,7 +13,8 @@ class StorageDirectoryManager { static StorageDirectoryManager get instance => _instance; - Future getFileStoreDirectory() async { + Future getFileStoreDirectory({bool isTemporary = true}) async { + if (!isTemporary) return (await getDownloadsDirectory())!.path; try { try { return (await getTemporaryDirectory()).path; @@ -28,10 +29,14 @@ class StorageDirectoryManager { Future getFilePathInAppDownloads({ required String eventId, required String fileName, + bool isTemporary = true, }) async { - final fileStoreDirectory = - await StorageDirectoryManager.instance.getFileStoreDirectory(); - return '$fileStoreDirectory/$eventId/$fileName'; + final fileStoreDirectory = await StorageDirectoryManager.instance + .getFileStoreDirectory(isTemporary: isTemporary); + if (isTemporary) { + return '$fileStoreDirectory/$eventId/$fileName'; + } + return '$fileStoreDirectory/${AppConfig.applicationName}/$fileName'; } Future getTwakeDownloadsFolderInDevice() async { @@ -71,9 +76,13 @@ class StorageDirectoryManager { Future getDecryptedFilePath({ required String eventId, required String fileName, + bool isTemporary = true, }) async { - final fileStoreDirectory = - await StorageDirectoryManager.instance.getFileStoreDirectory(); - return '$fileStoreDirectory/$eventId/decrypted-$fileName'; + final fileStoreDirectory = await StorageDirectoryManager.instance + .getFileStoreDirectory(isTemporary: isTemporary); + if (isTemporary) { + return '$fileStoreDirectory/$eventId/decrypted-$fileName'; + } + return '$fileStoreDirectory/${AppConfig.applicationName}/decrypted-$fileName'; } } diff --git a/lib/utils/matrix_sdk_extensions/download_file_extension.dart b/lib/utils/matrix_sdk_extensions/download_file_extension.dart index 1b8483cd54..924c9adcce 100644 --- a/lib/utils/matrix_sdk_extensions/download_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions/download_file_extension.dart @@ -43,11 +43,13 @@ extension DownloadFileExtension on Event { bool getThumbnail = false, CancelToken? cancelToken, required String filename, + bool isTemporary = true, }) async { final attachment = File( await StorageDirectoryManager.instance.getFilePathInAppDownloads( eventId: eventId, fileName: filename, + isTemporary: isTemporary, ), ); final downloadLink = mxcUrl.getDownloadLink(room.client); @@ -155,6 +157,7 @@ extension DownloadFileExtension on Event { required String filename, bool getThumbnail = false, StreamController>? streamController, + bool isTemporary = true, }) async { streamController?.add( const Right( @@ -168,6 +171,7 @@ extension DownloadFileExtension on Event { await StorageDirectoryManager.instance.getDecryptedFilePath( eventId: eventId, fileName: filename, + isTemporary: isTemporary, ), getThumbnail: getThumbnail, ); @@ -180,6 +184,7 @@ extension DownloadFileExtension on Event { await StorageDirectoryManager.instance.getDecryptedFilePath( eventId: eventId, fileName: filename, + isTemporary: isTemporary, ), ).copySync(savePath); streamController?.add( @@ -260,6 +265,7 @@ extension DownloadFileExtension on Event { StreamController>? downloadStreamController, ProgressCallback? progressCallback, CancelToken? cancelToken, + bool isTemporary = true, }) async { if (!canContainAttachment()) { throw ("getFileInfo: This event has the type '$type' and so it can't contain an attachment."); @@ -289,6 +295,7 @@ extension DownloadFileExtension on Event { await StorageDirectoryManager.instance.getDecryptedFilePath( eventId: eventId, fileName: filename, + isTemporary: isTemporary, ); final decryptedFile = File(decryptedPath); @@ -308,13 +315,17 @@ extension DownloadFileExtension on Event { return downloadOrRetrieveAttachment( mxcUrl, - await StorageDirectoryManager.instance - .getFilePathInAppDownloads(eventId: eventId, fileName: filename), + await StorageDirectoryManager.instance.getFilePathInAppDownloads( + eventId: eventId, + fileName: filename, + isTemporary: isTemporary, + ), downloadStreamController: downloadStreamController, getThumbnail: getThumbnail, progressCallback: progressCallback, cancelToken: cancelToken, filename: filename, + isTemporary: isTemporary, ); } } diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index 9c78247ab3..3c002d485c 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -185,7 +185,7 @@ extension LocalizedBody on Event { bool get isPinned => room.pinnedEventIds.contains(eventId); Future copy(BuildContext context, Timeline timeline) async { - if (messageType == MessageTypes.Image && PlatformInfos.isWeb) { + if (messageType == MessageTypes.Image && PlatformInfos.isWebOrDesktop) { final matrixFile = getMatrixFile() ?? await downloadAndDecryptAttachment( getThumbnail: true, diff --git a/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart b/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart index 1930cbd93c..9edad87c96 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_file_extension.dart @@ -16,7 +16,7 @@ import 'package:file_saver/file_saver.dart'; extension MatrixFileExtension on MatrixFile { Future downloadFile(BuildContext context) async { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { return await downloadFileInWeb(context); } @@ -51,11 +51,21 @@ extension MatrixFileExtension on MatrixFile { name: name, bytes: bytes, ); + + TwakeSnackBar.show( + context, + L10n.of(context)!.fileSavedToDownloads, + ); return '$directory/$name'; } catch (e) { Logs().e( "MatrixFileExtension()::downloadFileInWeb()::Error: $e", ); + + TwakeSnackBar.show( + context, + L10n.of(context)!.downloadImageError, + ); } return null; } diff --git a/lib/widgets/mixins/download_file_on_mobile_mixin.dart b/lib/widgets/mixins/download_file_on_mobile_mixin.dart index 4a2a438835..17e1ae39a4 100644 --- a/lib/widgets/mixins/download_file_on_mobile_mixin.dart +++ b/lib/widgets/mixins/download_file_on_mobile_mixin.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/utils/manager/storage_directory_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/download_file_extension.dart'; import 'package:fluffychat/utils/manager/download_manager/download_manager.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -70,6 +71,7 @@ mixin DownloadFileOnMobileMixin on State { await StorageDirectoryManager.instance.getFilePathInAppDownloads( eventId: event.eventId, fileName: event.filename, + isTemporary: !PlatformInfos.isDesktop, ); final file = File(filePath); if (await file.exists() && await file.length() == event.getFileSize()) { @@ -119,6 +121,7 @@ mixin DownloadFileOnMobileMixin on State { downloadFileStateNotifier.value = const DownloadingPresentationState(); downloadManager.download( event: event, + isTemporary: !PlatformInfos.isDesktop, ); _trySetupDownloadingStreamSubcription(); } diff --git a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart index 6af3307dfc..21bf5c5008 100644 --- a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart +++ b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/preview_file/download_file_for_preview_failure.dart'; import 'package:fluffychat/domain/app_state/preview_file/download_file_for_preview_loading.dart'; @@ -16,7 +18,7 @@ import 'package:fluffychat/utils/twake_snackbar.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; -import 'package:open_file/open_file.dart'; +import 'package:open_app_file/open_app_file.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:share_plus/share_plus.dart'; @@ -174,6 +176,13 @@ mixin HandleDownloadAndPreviewFileMixin { ); return; } + + if (PlatformInfos.isDesktop) { + _openDownloadedFileOnDesktop( + filePath: filePath, + mimeType: mimeType, + ); + } } void _openDownloadedFileForPreviewAndroid({ @@ -184,9 +193,9 @@ mixin HandleDownloadAndPreviewFileMixin { await Share.shareXFiles([XFile(filePath)]); return; } - final openResults = await OpenFile.open( + final openResults = await OpenAppFile.open( filePath, - type: mimeType, + mimeType: mimeType, uti: DocumentUti(SupportedPreviewFileTypes.iOSSupportedTypes[mimeType]) .value, ); @@ -207,12 +216,41 @@ mixin HandleDownloadAndPreviewFileMixin { Logs().d( 'ChatController:_openDownloadedFileForPreviewIos(): $filePath', ); - await OpenFile.open( + await OpenAppFile.open( filePath, - type: mimeType, + mimeType: mimeType, ); } + void _openDownloadedFileOnDesktop({ + required String filePath, + required String? mimeType, + }) async { + Logs().d( + 'ChatController:_openDownloadedFileOnDesktop(): $filePath', + ); + final downloadDirectory = await getDownloadsDirectory(); + try { + await OpenAppFile.open( + filePath, + mimeType: mimeType, + ); + } catch (e) { + Logs().e( + 'ChatController:_openDownloadedFileOnDesktop(): $e', + ); + if (downloadDirectory == null) { + return; + } + if (PlatformInfos.isLinux || PlatformInfos.isMacOS) { + Process.run('open', [downloadDirectory.path]); + } + if (PlatformInfos.isWindows) { + Process.run('explorer', [downloadDirectory.path]); + } + } + } + Future previewPdfWeb( BuildContext context, Event event, { diff --git a/pubspec.lock b/pubspec.lock index 9c80dfc886..877796f6d1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1805,14 +1805,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - open_file: + open_app_file: dependency: "direct main" description: - name: open_file - sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20 - url: "https://pub.dev" - source: hosted - version: "3.3.2" + path: "." + ref: HEAD + resolved-ref: "7054e90c4632af0a47be93d1b8891dc499bc5d6d" + url: "git@github.com:aws1313/open_app_file.git" + source: git + version: "4.0.1" overflow_view: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f621525be3..06b03bc0ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -150,7 +150,6 @@ dependencies: tuple: ^2.0.2 lottie: ^2.3.2 wechat_camera_picker: 4.2.1 - open_file: ^3.3.2 mime: ^1.0.4 async: ^2.11.0 cached_network_image: ^3.2.3 @@ -175,6 +174,9 @@ dependencies: flutter_portal: 1.1.4 external_path: 1.0.3 gal: 2.3.0 + open_app_file: + git: + url: git@github.com:aws1313/open_app_file.git dev_dependencies: build_runner: ^2.3.3 From 74fdc6571cb9628f20eb31988e093b20048120ea Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Tue, 30 Apr 2024 14:18:52 +0200 Subject: [PATCH 05/18] TW-1699: media viewers fixed --- .../media/chat_details_media_page.dart | 2 +- lib/presentation/mixins/play_video_action_mixin.dart | 4 ++-- lib/widgets/mxc_image.dart | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat_details/chat_details_page_view/media/chat_details_media_page.dart b/lib/pages/chat_details/chat_details_page_view/media/chat_details_media_page.dart index 1ca9f2278d..a1dd6015ea 100644 --- a/lib/pages/chat_details/chat_details_page_view/media/chat_details_media_page.dart +++ b/lib/pages/chat_details/chat_details_page_view/media/chat_details_media_page.dart @@ -130,7 +130,7 @@ class _VideoItem extends StatelessWidget { Future _onTapVideo(BuildContext context) async { final result = await Navigator.of( context, - rootNavigator: PlatformInfos.isWeb, + rootNavigator: PlatformInfos.isWebOrDesktop, ).push( HeroPageRoute( builder: (context) { diff --git a/lib/presentation/mixins/play_video_action_mixin.dart b/lib/presentation/mixins/play_video_action_mixin.dart index 507c948c28..b7e16c55ea 100644 --- a/lib/presentation/mixins/play_video_action_mixin.dart +++ b/lib/presentation/mixins/play_video_action_mixin.dart @@ -29,11 +29,11 @@ mixin PlayVideoActionMixin { }, ); if (isReplacement) { - Navigator.of(context, rootNavigator: PlatformInfos.isWeb).pushReplacement( + Navigator.of(context, rootNavigator: PlatformInfos.isWebOrDesktop).pushReplacement( pageRoute, ); } else { - Navigator.of(context, rootNavigator: PlatformInfos.isWeb).push( + Navigator.of(context, rootNavigator: PlatformInfos.isWebOrDesktop).push( pageRoute, ); } diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index c0ede95b7a..21498cd125 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -217,8 +217,10 @@ class _MxcImageState extends State { void _onTap(BuildContext context) async { if (widget.onTapPreview != null) { widget.onTapPreview!(); - final result = - await Navigator.of(context, rootNavigator: PlatformInfos.isWeb).push( + final result = await Navigator.of( + context, + rootNavigator: PlatformInfos.isWebOrDesktop, + ).push( HeroPageRoute( builder: (context) { return InteractiveViewerGallery( From 71d03d33f47a3df6ccf462dc3ffc77dfe1aab99e Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Fri, 3 May 2024 12:58:59 +0200 Subject: [PATCH 06/18] TW-1699: chat input shortcut fixed --- lib/pages/chat/chat_input_row.dart | 80 ++++++++++++------- lib/pages/chat/chat_input_row_mobile.dart | 64 ++++++--------- lib/pages/chat/chat_input_row_web.dart | 58 ++++++-------- .../mixins/handle_clipboard_action_mixin.dart | 7 ++ lib/utils/shortcuts.dart | 9 +++ 5 files changed, 119 insertions(+), 99 deletions(-) create mode 100644 lib/utils/shortcuts.dart diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 0557ad104b..3e6765f8c6 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -5,10 +5,12 @@ import 'package:fluffychat/pages/chat/chat_input_row_web.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/resource/image_paths.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/shortcuts.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:matrix/matrix.dart'; @@ -98,35 +100,57 @@ class ChatInputRow extends StatelessWidget { ); } - InputBar _buildInputBar(BuildContext context) { - return InputBar( - typeAheadKey: controller.chatComposerTypeAheadKey, - rawKeyboardFocusNode: controller.rawKeyboardListenerFocusNode, - room: controller.room!, - minLines: 1, - maxLines: 8, - autofocus: !PlatformInfos.isMobile, - keyboardType: TextInputType.multiline, - textInputAction: null, - onSubmitted: (_) => controller.onInputBarSubmitted(), - suggestionsController: controller.suggestionsController, - typeAheadFocusNode: controller.inputFocus, - controller: controller.sendController, - focusSuggestionController: controller.focusSuggestionController, - suggestionScrollController: controller.suggestionScrollController, - showEmojiPickerNotifier: controller.showEmojiPickerNotifier, - decoration: InputDecoration( - hintText: L10n.of(context)!.chatMessage, - hintMaxLines: 1, - hintStyle: Theme.of(context) - .textTheme - .bodyLarge - ?.merge( - Theme.of(context).inputDecorationTheme.hintStyle, - ) - .copyWith(letterSpacing: -0.15), + Widget _buildInputBar(BuildContext context) { + return Shortcuts( + shortcuts: { + LogicalKeySet( + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.keyA, + ): const SelectAllIntent(), + LogicalKeySet( + LogicalKeyboardKey.altLeft, + LogicalKeyboardKey.keyE, + ): const OnEmojiActionIntent(), + }, + child: Actions( + actions: >{ + SelectAllIntent: CallbackAction( + onInvoke: (_) => controller.selectAll(), + ), + OnEmojiActionIntent: CallbackAction( + onInvoke: (_) => controller.onEmojiAction(), + ), + }, + child: InputBar( + typeAheadKey: controller.chatComposerTypeAheadKey, + rawKeyboardFocusNode: controller.rawKeyboardListenerFocusNode, + room: controller.room!, + minLines: 1, + maxLines: 8, + autofocus: !PlatformInfos.isMobile, + keyboardType: TextInputType.multiline, + textInputAction: null, + onSubmitted: (_) => controller.onInputBarSubmitted(), + suggestionsController: controller.suggestionsController, + typeAheadFocusNode: controller.inputFocus, + controller: controller.sendController, + focusSuggestionController: controller.focusSuggestionController, + suggestionScrollController: controller.suggestionScrollController, + showEmojiPickerNotifier: controller.showEmojiPickerNotifier, + decoration: InputDecoration( + hintText: L10n.of(context)!.chatMessage, + hintMaxLines: 1, + hintStyle: Theme.of(context) + .textTheme + .bodyLarge + ?.merge( + Theme.of(context).inputDecorationTheme.hintStyle, + ) + .copyWith(letterSpacing: -0.15), + ), + onChanged: controller.onInputBarChanged, + ), ), - onChanged: controller.onInputBarChanged, ); } } diff --git a/lib/pages/chat/chat_input_row_mobile.dart b/lib/pages/chat/chat_input_row_mobile.dart index 0b8e310d67..c1efea26f4 100644 --- a/lib/pages/chat/chat_input_row_mobile.dart +++ b/lib/pages/chat/chat_input_row_mobile.dart @@ -2,9 +2,7 @@ import 'package:animations/animations.dart'; import 'package:fluffychat/pages/chat/chat_input_row_style.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; typedef OnTapEmojiAction = void Function(); @@ -48,44 +46,34 @@ class ChatInputRowMobile extends StatelessWidget { Expanded( child: inputBar, ), - KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.altLeft, - LogicalKeyboardKey.keyE, - }, - onKeysPressed: onEmojiAction, - helpLabel: L10n.of(context)!.emojis, - child: InkWell( - onTap: onEmojiAction, - hoverColor: Colors.transparent, - child: PageTransitionSwitcher( - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.scaled, - fillColor: Colors.transparent, - child: child, + InkWell( + onTap: onEmojiAction, + hoverColor: Colors.transparent, + child: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + fillColor: Colors.transparent, + child: child, + ); + }, + child: ValueListenableBuilder( + valueListenable: emojiPickerNotifier, + builder: (context, showEmojiPicker, child) { + return TwakeIconButton( + paddingAll: + ChatInputRowStyle.chatInputRowPaddingBtnMobile, + tooltip: L10n.of(context)!.emojis, + onTap: showEmojiPicker ? onKeyboardAction : onEmojiAction, + icon: showEmojiPicker ? Icons.keyboard : Icons.tag_faces, ); }, - child: ValueListenableBuilder( - valueListenable: emojiPickerNotifier, - builder: (context, showEmojiPicker, child) { - return TwakeIconButton( - paddingAll: - ChatInputRowStyle.chatInputRowPaddingBtnMobile, - tooltip: L10n.of(context)!.emojis, - onTap: - showEmojiPicker ? onKeyboardAction : onEmojiAction, - icon: - showEmojiPicker ? Icons.keyboard : Icons.tag_faces, - ); - }, - ), ), ), ), diff --git a/lib/pages/chat/chat_input_row_web.dart b/lib/pages/chat/chat_input_row_web.dart index 5448c2e5c8..5bf160c3c7 100644 --- a/lib/pages/chat/chat_input_row_web.dart +++ b/lib/pages/chat/chat_input_row_web.dart @@ -2,9 +2,7 @@ import 'package:animations/animations.dart'; import 'package:fluffychat/pages/chat/chat_input_row_style.dart'; import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; @@ -54,39 +52,33 @@ class ChatInputRowWeb extends StatelessWidget { Expanded( child: inputBar, ), - KeyBoardShortcuts( - keysToPress: {LogicalKeyboardKey.altLeft, LogicalKeyboardKey.keyE}, - onKeysPressed: onEmojiAction, - helpLabel: L10n.of(context)!.emojis, - child: InkWell( - onTap: onEmojiAction, - hoverColor: Colors.transparent, - child: PageTransitionSwitcher( - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.scaled, - fillColor: Colors.transparent, - child: child, + InkWell( + onTap: onEmojiAction, + hoverColor: Colors.transparent, + child: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + fillColor: Colors.transparent, + child: child, + ); + }, + child: ValueListenableBuilder( + valueListenable: emojiPickerNotifier, + builder: (context, showEmojiPicker, child) { + return TwakeIconButton( + paddingAll: ChatInputRowStyle.chatInputRowPaddingBtnMobile, + tooltip: L10n.of(context)!.emojis, + onTap: showEmojiPicker ? onKeyboardAction : onEmojiAction, + icon: showEmojiPicker ? Icons.keyboard : Icons.tag_faces, ); }, - child: ValueListenableBuilder( - valueListenable: emojiPickerNotifier, - builder: (context, showEmojiPicker, child) { - return TwakeIconButton( - paddingAll: - ChatInputRowStyle.chatInputRowPaddingBtnMobile, - tooltip: L10n.of(context)!.emojis, - onTap: showEmojiPicker ? onKeyboardAction : onEmojiAction, - icon: showEmojiPicker ? Icons.keyboard : Icons.tag_faces, - ); - }, - ), ), ), ), diff --git a/lib/presentation/mixins/handle_clipboard_action_mixin.dart b/lib/presentation/mixins/handle_clipboard_action_mixin.dart index c203eb39b3..636cb20304 100644 --- a/lib/presentation/mixins/handle_clipboard_action_mixin.dart +++ b/lib/presentation/mixins/handle_clipboard_action_mixin.dart @@ -22,6 +22,13 @@ mixin HandleClipboardActionMixin on PasteImageMixin { ClipboardEvents.instance?.unregisterPasteEventListener(_onPasteEvent); } + void selectAll() { + sendController.selection = TextSelection( + baseOffset: 0, + extentOffset: sendController.text.length, + ); + } + void _onPasteEvent(ClipboardReadEvent event) async { if (chatFocusNode.hasFocus != true) { return; diff --git a/lib/utils/shortcuts.dart b/lib/utils/shortcuts.dart new file mode 100644 index 0000000000..ffa994eac8 --- /dev/null +++ b/lib/utils/shortcuts.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +class OnEmojiActionIntent extends Intent { + const OnEmojiActionIntent(); +} + +class SelectAllIntent extends Intent { + const SelectAllIntent(); +} From 7ca19a8fa5f6067dd66322ed17da7a944db00adc Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Fri, 3 May 2024 17:13:58 +0200 Subject: [PATCH 07/18] TW-1699: send files ok --- .../model/extensions/xfile_extension.dart | 15 ++++++++++++++ lib/pages/chat/chat.dart | 3 +++ lib/presentation/mixins/send_files_mixin.dart | 20 +++++++++++++++++++ pubspec.lock | 2 +- pubspec.yaml | 1 + 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 lib/domain/model/extensions/xfile_extension.dart diff --git a/lib/domain/model/extensions/xfile_extension.dart b/lib/domain/model/extensions/xfile_extension.dart new file mode 100644 index 0000000000..d36f7c5f92 --- /dev/null +++ b/lib/domain/model/extensions/xfile_extension.dart @@ -0,0 +1,15 @@ +import 'package:file_selector/file_selector.dart'; +import 'package:matrix/matrix.dart'; + +extension XFileExtension on XFile { + + Future toMatrixFile() async{ + return MatrixFile.fromMimeType( + bytes: await readAsBytes(), + mimeType: mimeType, + name: name, + filePath: path, + sizeInBytes: await length(), + ); + } +} \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index bd5d9ad3f3..c3157ea555 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1284,6 +1284,9 @@ class ChatController extends State void onSendFileClick(BuildContext context) async { if (PlatformInfos.isMobile) { _showMediaPicker(context); + } else if (PlatformInfos.isDesktop) { + final matrixFiles = await pickFilesFromDesktop(); + sendFileOnWebAction(context, room: room, matrixFilesList: matrixFiles); } else { final matrixFiles = await pickFilesFromSystem(); sendFileOnWebAction(context, room: room, matrixFilesList: matrixFiles); diff --git a/lib/presentation/mixins/send_files_mixin.dart b/lib/presentation/mixins/send_files_mixin.dart index 1b7fa60c87..756e2393c3 100644 --- a/lib/presentation/mixins/send_files_mixin.dart +++ b/lib/presentation/mixins/send_files_mixin.dart @@ -1,12 +1,14 @@ import 'package:file_picker/file_picker.dart'; import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/model/extensions/platform_file/platform_file_extension.dart'; +import 'package:fluffychat/domain/model/extensions/xfile_extension.dart'; import 'package:fluffychat/domain/usecase/send_file_interactor.dart'; import 'package:fluffychat/domain/usecase/send_images_interactor.dart'; import 'package:fluffychat/pages/chat/chat_actions.dart'; import 'package:fluffychat/presentation/model/file/file_asset_entity.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/images_picker/images_picker.dart'; +import 'package:file_selector/file_selector.dart'; import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; @@ -68,6 +70,24 @@ mixin SendFilesMixin { return result.files.map((file) => file.toMatrixFileOnWeb()).toList(); } + Future> pickFilesFromDesktop() async { + final String initialDirectory = + (await getApplicationDocumentsDirectory()).path; + final List xFiles = + await openFiles(initialDirectory: initialDirectory); + + if (xFiles.isEmpty) return []; + + final matrixFiles = []; + + for (final xFile in xFiles) { + final matrixFile = await xFile.toMatrixFile(); + matrixFiles.add(matrixFile); + } + + return matrixFiles; + } + void onPickerTypeClick({ required BuildContext context, Room? room, diff --git a/pubspec.lock b/pubspec.lock index 877796f6d1..30f1f8ed1a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -619,7 +619,7 @@ packages: source: hosted version: "0.2.12" file_selector: - dependency: "direct overridden" + dependency: "direct main" description: name: file_selector sha256: "1d2fde93dddf634a9c3c0faa748169d7ac0d83757135555707e52f02c017ad4f" diff --git a/pubspec.yaml b/pubspec.yaml index 06b03bc0ca..fe881ff602 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -177,6 +177,7 @@ dependencies: open_app_file: git: url: git@github.com:aws1313/open_app_file.git + file_selector: ^0.9.2+2 dev_dependencies: build_runner: ^2.3.3 From b7cec7a3e90d3149def04bf2bc7942327fae9cc4 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Fri, 3 May 2024 17:54:55 +0200 Subject: [PATCH 08/18] TW-1699: change avatar updated --- .../model/extensions/xfile_extension.dart | 15 +++++-- .../settings_profile/settings_profile.dart | 45 ++++++++++++++++++- .../settings_profile_view_mobile.dart | 4 +- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/lib/domain/model/extensions/xfile_extension.dart b/lib/domain/model/extensions/xfile_extension.dart index d36f7c5f92..684045c614 100644 --- a/lib/domain/model/extensions/xfile_extension.dart +++ b/lib/domain/model/extensions/xfile_extension.dart @@ -1,9 +1,9 @@ +import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:matrix/matrix.dart'; extension XFileExtension on XFile { - - Future toMatrixFile() async{ + Future toMatrixFile() async { return MatrixFile.fromMimeType( bytes: await readAsBytes(), mimeType: mimeType, @@ -12,4 +12,13 @@ extension XFileExtension on XFile { sizeInBytes: await length(), ); } -} \ No newline at end of file + + Future toPlatformFile() async { + return PlatformFile.fromMap({ + 'name': name, + 'path': path, + 'bytes': await readAsBytes(), + 'size': await length(), + }); + } +} diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart index ada07b9f99..e1b20ba300 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/room/upload_content_state.dart'; import 'package:fluffychat/domain/app_state/settings/update_profile_failure.dart'; import 'package:fluffychat/domain/app_state/settings/update_profile_success.dart'; +import 'package:fluffychat/domain/model/extensions/xfile_extension.dart'; import 'package:fluffychat/domain/usecase/room/upload_content_for_web_interactor.dart'; import 'package:fluffychat/domain/usecase/room/upload_content_interactor.dart'; import 'package:fluffychat/domain/usecase/settings/update_profile_interactor.dart'; @@ -33,6 +34,8 @@ import 'package:linagora_design_flutter/images_picker/asset_counter.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:wechat_camera_picker/wechat_camera_picker.dart'; class SettingsProfile extends StatefulWidget { @@ -180,7 +183,43 @@ class SettingsProfileController extends State ), ); Logs().d( - 'SettingsProfile::_getImageOnWeb(): AvatarWebNotifier - $result', + 'SettingsProfile::_getImageOnWeb(): AvatarNotifier - $result', + ); + } + } + + void _getImageOnDesktop( + BuildContext context, + ) async { + const XTypeGroup typeGroup = XTypeGroup( + label: 'images', + extensions: ['jpg', 'png'], + ); + final String initialDirectory = + (await getApplicationDocumentsDirectory()).path; + + final XFile? result = await openFile( + initialDirectory: initialDirectory, + acceptedTypeGroups: [typeGroup], + ); + + Logs().d( + 'SettingsProfile::_getImageOnDesktop(): FilePickerResult - ${result?.path}', + ); + + if (result == null) { + return; + } else { + if (!isEditedProfileNotifier.value) { + isEditedProfileNotifier.toggle(); + } + settingsProfileUIState.value = Right( + GetAvatarInBytesUIStateSuccess( + filePickerResult: FilePickerResult([await result.toPlatformFile()]), + ), + ); + Logs().d( + 'SettingsProfile::_getImageOnDesktop(): AvatarNotifier - $result', ); } } @@ -190,6 +229,10 @@ class SettingsProfileController extends State _getImageOnWeb(context); return; } + if (PlatformInfos.isDesktop) { + _getImageOnDesktop(context); + return; + } final currentPermissionPhotos = await getCurrentMediaPermission(); if (currentPermissionPhotos != null) { final imagePickerController = createImagePickerController(); diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart index c3d36bcc7b..184f03d67a 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile_view_mobile.dart @@ -93,7 +93,7 @@ class SettingsProfileViewMobile extends StatelessWidget { ); } if (success is GetAvatarInBytesUIStateSuccess && - PlatformInfos.isWeb) { + PlatformInfos.isWebOrDesktop) { if (success.filePickerResult == null || success.filePickerResult?.files.single.bytes == null) { @@ -160,7 +160,7 @@ class SettingsProfileViewMobile extends StatelessWidget { ) { return GestureDetector( onTap: () { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { menuController.isOpen ? menuController.close() : menuController.open(); From 0272691b006a6f3103a02032aba5f7e8d0d75e6f Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Mon, 6 May 2024 15:41:37 +0200 Subject: [PATCH 09/18] fixup! TW-1699: change avatar updated --- lib/presentation/mixins/play_video_action_mixin.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/mixins/play_video_action_mixin.dart b/lib/presentation/mixins/play_video_action_mixin.dart index b7e16c55ea..6a5cc77889 100644 --- a/lib/presentation/mixins/play_video_action_mixin.dart +++ b/lib/presentation/mixins/play_video_action_mixin.dart @@ -29,7 +29,8 @@ mixin PlayVideoActionMixin { }, ); if (isReplacement) { - Navigator.of(context, rootNavigator: PlatformInfos.isWebOrDesktop).pushReplacement( + Navigator.of(context, rootNavigator: PlatformInfos.isWebOrDesktop) + .pushReplacement( pageRoute, ); } else { From 91419cffde7bbf0a6661589edbce39f02b82bf1d Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Tue, 7 May 2024 15:13:05 +0200 Subject: [PATCH 10/18] fixup! fixup! TW-1699: change avatar updated --- lib/config/go_routes/go_router.dart | 2 +- lib/domain/model/extensions/xfile_extension.dart | 2 ++ lib/pages/image_viewer/image_viewer.dart | 2 +- .../settings_profile/settings_profile.dart | 7 ++----- lib/presentation/mixins/connect_page_mixin.dart | 2 +- lib/utils/xfile_groups.dart | 8 ++++++++ 6 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 lib/utils/xfile_groups.dart diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index c030f8f853..980c5f4ba1 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -78,7 +78,7 @@ abstract class AppRoutes { path: '/home', pageBuilder: (context, state) => defaultPageBuilder( context, - PlatformInfos.isMobile || PlatformInfos.isLinux + PlatformInfos.isMobile || PlatformInfos.isDesktop ? const TwakeWelcome() : AutoHomeserverPicker( loggedOut: state.extra is bool ? state.extra as bool? : null, diff --git a/lib/domain/model/extensions/xfile_extension.dart b/lib/domain/model/extensions/xfile_extension.dart index 684045c614..1729c12664 100644 --- a/lib/domain/model/extensions/xfile_extension.dart +++ b/lib/domain/model/extensions/xfile_extension.dart @@ -10,6 +10,7 @@ extension XFileExtension on XFile { name: name, filePath: path, sizeInBytes: await length(), + readStream: readAsBytes().asStream(), ); } @@ -19,6 +20,7 @@ extension XFileExtension on XFile { 'path': path, 'bytes': await readAsBytes(), 'size': await length(), + 'readStream': readAsBytes().asStream(), }); } } diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index 0dececb5a4..2f8577f63b 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -45,7 +45,7 @@ class ImageViewerController extends State { @override void initState() { super.initState(); - if (!PlatformInfos.isWebOrDesktop && widget.event != null) { + if (PlatformInfos.isMobile && widget.event != null) { handleDownloadFile(widget.event!); } } diff --git a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart index e1b20ba300..3957b7de50 100644 --- a/lib/pages/settings_dashboard/settings_profile/settings_profile.dart +++ b/lib/pages/settings_dashboard/settings_profile/settings_profile.dart @@ -25,6 +25,7 @@ import 'package:fluffychat/utils/dialog/twake_dialog.dart'; import 'package:fluffychat/utils/extension/value_notifier_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/twake_snackbar.dart'; +import 'package:fluffychat/utils/xfile_groups.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mixins/popup_context_menu_action_mixin.dart'; import 'package:fluffychat/widgets/mixins/popup_menu_widget_mixin.dart'; @@ -191,16 +192,12 @@ class SettingsProfileController extends State void _getImageOnDesktop( BuildContext context, ) async { - const XTypeGroup typeGroup = XTypeGroup( - label: 'images', - extensions: ['jpg', 'png'], - ); final String initialDirectory = (await getApplicationDocumentsDirectory()).path; final XFile? result = await openFile( initialDirectory: initialDirectory, - acceptedTypeGroups: [typeGroup], + acceptedTypeGroups: [XFileGroups.images], ); Logs().d( diff --git a/lib/presentation/mixins/connect_page_mixin.dart b/lib/presentation/mixins/connect_page_mixin.dart index ea93279ce9..071aa3ce27 100644 --- a/lib/presentation/mixins/connect_page_mixin.dart +++ b/lib/presentation/mixins/connect_page_mixin.dart @@ -113,7 +113,7 @@ mixin ConnectPageMixin { ); final urlScheme = _getRedirectUrlScheme(redirectUrl); - return FlutterWebAuth2.authenticate( + return await FlutterWebAuth2.authenticate( url: url, callbackUrlScheme: urlScheme, options: const FlutterWebAuth2Options( diff --git a/lib/utils/xfile_groups.dart b/lib/utils/xfile_groups.dart new file mode 100644 index 0000000000..ba18224227 --- /dev/null +++ b/lib/utils/xfile_groups.dart @@ -0,0 +1,8 @@ +import 'package:file_selector/file_selector.dart'; + +class XFileGroups { + static const XTypeGroup images = XTypeGroup( + label: 'images', + extensions: ['jpg', 'png'], + ); +} From ab5da590380f4dd2b4387edd745b9a638d7bd651 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Mon, 13 May 2024 14:21:10 +0200 Subject: [PATCH 11/18] fixup! fixup! fixup! TW-1699: change avatar updated --- lib/pages/bootstrap/bootstrap_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index b948ae7a16..bca0fa0442 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -67,7 +67,7 @@ class BootstrapDialogState extends State { if (PlatformInfos.isAndroid) { return L10n.of(context)!.storeInAndroidKeystore; } - if (PlatformInfos.isIOS || PlatformInfos.isMacOS || PlatformInfos.isLinux) { + if (PlatformInfos.isIOS || PlatformInfos.isMacOS) { return L10n.of(context)!.storeInAppleKeyChain; } return L10n.of(context)!.storeSecurlyOnThisDevice; From 059f1e4eb22237e75a073b8ad4ea501b9a35c3c8 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Mon, 13 May 2024 16:56:29 +0200 Subject: [PATCH 12/18] TW-1699: ADR added for open file package change --- ...atus.md => 0022-listen-to-presence-status.md} | 4 ++-- docs/adr/0023-change-open-file-package.md | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) rename docs/adr/{0021-listen-to-presence-status.md => 0022-listen-to-presence-status.md} (98%) create mode 100644 docs/adr/0023-change-open-file-package.md diff --git a/docs/adr/0021-listen-to-presence-status.md b/docs/adr/0022-listen-to-presence-status.md similarity index 98% rename from docs/adr/0021-listen-to-presence-status.md rename to docs/adr/0022-listen-to-presence-status.md index ca5c2da4aa..9c9eed261e 100644 --- a/docs/adr/0021-listen-to-presence-status.md +++ b/docs/adr/0022-listen-to-presence-status.md @@ -1,4 +1,4 @@ -# 21. Listen to presence status +# 22. Listen to presence status Date: 2024-04-08 @@ -61,4 +61,4 @@ Here `lastActivePresence` is updated for each items in `sync.presence` list if i if (lastActivePresence != null) { onlatestPresenceChanged.add(lastActivePresence); } -``` \ No newline at end of file +``` diff --git a/docs/adr/0023-change-open-file-package.md b/docs/adr/0023-change-open-file-package.md new file mode 100644 index 0000000000..803739a973 --- /dev/null +++ b/docs/adr/0023-change-open-file-package.md @@ -0,0 +1,16 @@ +# 23. Change open file package + +Date: 2024-05-13 + +## Status + +Accepted + +## Context + +The package `open_file` has been used to open files on mobile versions of the app. The problem is that this package is not compatible with desktop platforms. Especially on Linux where it caused some errors and does not work. That said we could use the method `Process.run()` for each desktop platform but that might complexify a lot the process. + +## Decision + +A fork of `open_file` has been made, named `open_file_app` (https://pub.dev/packages/open_app_file). A fix has been made for Linux https://github.com/yendoplan/open_app_file/pull/5 which is the branch we will use until it will be merged by the maintainers. +Since it's a fork, the methods are the same than `open_file` and works the same way. From 4ae5c04c49376b207c611b9acc643e313e5ec4ce Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Tue, 14 May 2024 09:14:12 +0200 Subject: [PATCH 13/18] TW-1699: ADR added for OIDC explanation --- docs/adr/0024-oidc-mechanism-on-desktop.md | 33 ++++++++++++++++++ .../mixins/connect_page_mixin.dart | 34 +++++++++++++------ 2 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 docs/adr/0024-oidc-mechanism-on-desktop.md diff --git a/docs/adr/0024-oidc-mechanism-on-desktop.md b/docs/adr/0024-oidc-mechanism-on-desktop.md new file mode 100644 index 0000000000..7793a044c3 --- /dev/null +++ b/docs/adr/0024-oidc-mechanism-on-desktop.md @@ -0,0 +1,33 @@ +# 24. OIDC mechanism on desktop + +Date: 2024-05-13 + +## Status + +Accepted + +## Context + +Currently OIDC is handled for web and mobile versions of the application. For web `FlutterWebAuth2` uses an `iframe` to watch the result of the log in process wether it's a success, or a timeout. For the case of mobile app, the app registered a url scheme which looks like `myapp://auth`. This scheme is where the OIDC server will send its result which will be retrieved by the app like a deeplink. + +But we can't use an `iframe` or register a custom url on desktop applications (at least for linux and windows). So we have to find an other solution to catch the result from the browser where the user log in. + +## Decision + +To achieve that the app (via `FlutterWebAuth2`) sets a light webserver on the user's device. This server's URI, which looks like `http://localhost:port`, uses on a random open port and is sent to OIDC server as form url encoded content. This port is found using this method: + +```dart + Future findFreePort() async { + // launch a local light web server + // to find a random open open, we set port as 0 + final tmpServer = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + final port = tmpServer.port; + // when an open port as been found tmp server is closed and port is returned + await tmpServer.close(); + return port; + } +``` + +The app listens to this server, looking for a result. If log in succeed, the OIDC server `POST` the access token to this server which send it to the app. +As soon as the log in process is done (wether it's successful or not), the webserver is closed. +More details: https://github.com/ThexXTURBOXx/flutter_web_auth_2/blob/b48b6f5c866b8c1018cc138b2b11acb3b6188e0b/flutter_web_auth_2/lib/src/server.dart diff --git a/lib/presentation/mixins/connect_page_mixin.dart b/lib/presentation/mixins/connect_page_mixin.dart index 071aa3ce27..369fa59f5f 100644 --- a/lib/presentation/mixins/connect_page_mixin.dart +++ b/lib/presentation/mixins/connect_page_mixin.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/auto_homeserver_picker/auto_homeserver_picker.dart'; @@ -23,9 +24,6 @@ mixin ConnectPageMixin { static const windowNameValue = '_self'; static const redirectPublicPlatformOnWeb = 'post_login_redirect_url'; - - static const linowsRedirectUrl = 'http://localhost:60665'; - bool supportsFlow({ required BuildContext context, required String flowType, @@ -62,7 +60,7 @@ mixin ConnectPageMixin { String _getRedirectUrlScheme(String redirectUrl) { if (PlatformInfos.isLinuxOrWindows) { - return linowsRedirectUrl; + return redirectUrl; } return Uri.parse(redirectUrl).scheme; } @@ -103,7 +101,7 @@ mixin ConnectPageMixin { required BuildContext context, required String id, }) async { - final redirectUrl = _generateRedirectUrl( + final redirectUrl = await _generateRedirectUrl( Matrix.of(context).client.homeserver.toString(), ); final url = _getAuthenticateUrl( @@ -168,7 +166,7 @@ mixin ConnectPageMixin { Future tryLogoutSso(BuildContext context) async { if (Matrix.of(context).loginType != LoginType.mLoginToken) return; - final redirectUrl = _generatePostLogoutRedirectUrl(); + final redirectUrl = await _generatePostLogoutRedirectUrl(); final url = _getLogoutUrl(context, redirectUrl: redirectUrl); if (url == null) return Future.value(); @@ -191,7 +189,7 @@ mixin ConnectPageMixin { required BuildContext context, required String id, }) async { - final redirectUrl = _generateRedirectUrl( + final redirectUrl = await _generateRedirectUrl( Matrix.of(context).client.homeserver.toString(), ); final url = generatePublicPlatformAuthenticationUrl( @@ -211,18 +209,22 @@ mixin ConnectPageMixin { Logs().d("ConnectPageMixin:_redirectRegistrationUrl: URI - $uri"); } - String _generatePostLogoutRedirectUrl() { + Future _generatePostLogoutRedirectUrl() async { if (kIsWeb) { if (AppConfig.issueId != null && AppConfig.issueId!.isNotEmpty) { return '${html.window.origin!}/twake-on-matrix/${AppConfig.issueId}/auth.html'; } return '${html.window.origin!}/web/auth.html'; + } else if (PlatformInfos.isLinuxOrWindows) { + return await _generateDesktopRedirectUrl(); } return '${AppConfig.appOpenUrlScheme.toLowerCase()}://redirect'; } - String _generateRedirectUrl(String homeserver) { - if (PlatformInfos.isLinuxOrWindows) return linowsRedirectUrl; + Future _generateRedirectUrl(String homeserver) async { + if (PlatformInfos.isLinuxOrWindows) { + return await _generateDesktopRedirectUrl(); + } if (kIsWeb) { String? homeserverParam = ''; if (homeserver.isNotEmpty) { @@ -236,6 +238,18 @@ mixin ConnectPageMixin { return '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'; } + Future _findFreePort() async { + final tmpServer = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + final port = tmpServer.port; + await tmpServer.close(); + return port; + } + + Future _generateDesktopRedirectUrl() async { + final freePort = await _findFreePort(); + return 'http://localhost:$freePort/callback'; + } + List? identityProviders({ Map? rawLoginTypes, }) { From 56c7470ec6b35f5472b084cacbceeebb8ef0dd25 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Tue, 14 May 2024 09:19:32 +0200 Subject: [PATCH 14/18] fixup! TW-1699: ADR added for OIDC explanation --- docs/adr/0024-oidc-mechanism-on-desktop.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/adr/0024-oidc-mechanism-on-desktop.md b/docs/adr/0024-oidc-mechanism-on-desktop.md index 7793a044c3..3e6608f074 100644 --- a/docs/adr/0024-oidc-mechanism-on-desktop.md +++ b/docs/adr/0024-oidc-mechanism-on-desktop.md @@ -30,4 +30,8 @@ To achieve that the app (via `FlutterWebAuth2`) sets a light webserver on the us The app listens to this server, looking for a result. If log in succeed, the OIDC server `POST` the access token to this server which send it to the app. As soon as the log in process is done (wether it's successful or not), the webserver is closed. -More details: https://github.com/ThexXTURBOXx/flutter_web_auth_2/blob/b48b6f5c866b8c1018cc138b2b11acb3b6188e0b/flutter_web_auth_2/lib/src/server.dart + +More details: +- https://github.com/ThexXTURBOXx/flutter_web_auth_2/blob/b48b6f5c866b8c1018cc138b2b11acb3b6188e0b/flutter_web_auth_2/lib/src/server.dart +- https://blog.logto.io/redirect-uri-in-authorization-code-flow/ +- https://openid.net/developers/how-connect-works/ From 1494bdfb05b6ae2da54a23b8be0599864bb95fd8 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Tue, 14 May 2024 12:00:42 +0200 Subject: [PATCH 15/18] fixup! fixup! TW-1699: ADR added for OIDC explanation --- .../settings_dashboard/settings/settings.dart | 2 +- lib/presentation/mixins/send_files_mixin.dart | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/pages/settings_dashboard/settings/settings.dart b/lib/pages/settings_dashboard/settings/settings.dart index 4a56c4edfd..2159c3fe9d 100644 --- a/lib/pages/settings_dashboard/settings/settings.dart +++ b/lib/pages/settings_dashboard/settings/settings.dart @@ -104,7 +104,7 @@ class SettingsController extends State with ConnectPageMixin { } catch (e) { Logs().e('SettingsController()::logoutAction - error: $e'); } finally { - if (PlatformInfos.isWeb) { + if (PlatformInfos.isWebOrDesktop) { await tryLogoutSso(context); } } diff --git a/lib/presentation/mixins/send_files_mixin.dart b/lib/presentation/mixins/send_files_mixin.dart index 756e2393c3..708388d13a 100644 --- a/lib/presentation/mixins/send_files_mixin.dart +++ b/lib/presentation/mixins/send_files_mixin.dart @@ -71,21 +71,26 @@ mixin SendFilesMixin { } Future> pickFilesFromDesktop() async { - final String initialDirectory = - (await getApplicationDocumentsDirectory()).path; - final List xFiles = - await openFiles(initialDirectory: initialDirectory); + try { + final String initialDirectory = + (await getApplicationDocumentsDirectory()).path; + final List xFiles = + await openFiles(initialDirectory: initialDirectory); - if (xFiles.isEmpty) return []; + if (xFiles.isEmpty) return []; - final matrixFiles = []; + final matrixFiles = []; - for (final xFile in xFiles) { - final matrixFile = await xFile.toMatrixFile(); - matrixFiles.add(matrixFile); - } + for (final xFile in xFiles) { + final matrixFile = await xFile.toMatrixFile(); + matrixFiles.add(matrixFile); + } - return matrixFiles; + return matrixFiles; + } on Exception catch (error) { + Logs().e('SendFilesMixin::pickFilesFromDesktop(): error: $error'); + return []; + } } void onPickerTypeClick({ From 51c900b6d56d088dd3c08f3901157938ba0af240 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Tue, 14 May 2024 13:49:02 +0200 Subject: [PATCH 16/18] fixup! fixup! fixup! TW-1699: ADR added for OIDC explanation --- lib/pages/chat/chat.dart | 6 +++--- .../mixins/send_files_with_caption_web_mixin.dart | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index c3157ea555..0a8c5f8964 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -375,7 +375,7 @@ class ChatController extends State void handleDragDone(DropDoneDetails details) async { final matrixFiles = await onDragDone(details); - sendFileOnWebAction(context, room: room, matrixFilesList: matrixFiles); + openSendFileDialogAction(context, room: room, matrixFilesList: matrixFiles); } void _handleReceivedShareFiles() { @@ -1286,10 +1286,10 @@ class ChatController extends State _showMediaPicker(context); } else if (PlatformInfos.isDesktop) { final matrixFiles = await pickFilesFromDesktop(); - sendFileOnWebAction(context, room: room, matrixFilesList: matrixFiles); + openSendFileDialogAction(context, room: room, matrixFilesList: matrixFiles); } else { final matrixFiles = await pickFilesFromSystem(); - sendFileOnWebAction(context, room: room, matrixFilesList: matrixFiles); + openSendFileDialogAction(context, room: room, matrixFilesList: matrixFiles); } } diff --git a/lib/presentation/mixins/send_files_with_caption_web_mixin.dart b/lib/presentation/mixins/send_files_with_caption_web_mixin.dart index 45949fa373..90d4234c2a 100644 --- a/lib/presentation/mixins/send_files_with_caption_web_mixin.dart +++ b/lib/presentation/mixins/send_files_with_caption_web_mixin.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/utils/twake_snackbar.dart'; import 'package:matrix/matrix.dart'; mixin SendFilesWithCaptionWebMixin { - void sendFileOnWebAction( + void openSendFileDialogAction( BuildContext context, { Room? room, required List matrixFilesList, From 0a9b803c5fed2eebae375cbdb637940704d8ab6c Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Tue, 14 May 2024 13:53:21 +0200 Subject: [PATCH 17/18] fixup! fixup! fixup! fixup! TW-1699: ADR added for OIDC explanation --- .../handle_download_and_preview_file_mixin.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart index 21bf5c5008..f64d073550 100644 --- a/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart +++ b/lib/widgets/mixins/handle_download_and_preview_file_mixin.dart @@ -242,12 +242,16 @@ mixin HandleDownloadAndPreviewFileMixin { if (downloadDirectory == null) { return; } - if (PlatformInfos.isLinux || PlatformInfos.isMacOS) { - Process.run('open', [downloadDirectory.path]); - } - if (PlatformInfos.isWindows) { - Process.run('explorer', [downloadDirectory.path]); - } + _openFileUsingTerminal(downloadDirectory.path); + } + } + + void _openFileUsingTerminal(String path) { + if (PlatformInfos.isLinux || PlatformInfos.isMacOS) { + Process.run('open', [path]); + } + if (PlatformInfos.isWindows) { + Process.run('explorer', [path]); } } From 2341ece448e07693c45a3dcd75a1c4e5f11f1e95 Mon Sep 17 00:00:00 2001 From: Terence Zafindratafa Date: Wed, 15 May 2024 09:27:38 +0200 Subject: [PATCH 18/18] fixup! fixup! fixup! fixup! fixup! TW-1699: ADR added for OIDC explanation --- lib/domain/model/extensions/xfile_extension.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/domain/model/extensions/xfile_extension.dart b/lib/domain/model/extensions/xfile_extension.dart index 1729c12664..684045c614 100644 --- a/lib/domain/model/extensions/xfile_extension.dart +++ b/lib/domain/model/extensions/xfile_extension.dart @@ -10,7 +10,6 @@ extension XFileExtension on XFile { name: name, filePath: path, sizeInBytes: await length(), - readStream: readAsBytes().asStream(), ); } @@ -20,7 +19,6 @@ extension XFileExtension on XFile { 'path': path, 'bytes': await readAsBytes(), 'size': await length(), - 'readStream': readAsBytes().asStream(), }); } }