From fe9c4871790fb795b7323507d92f0aff108a0070 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Sat, 16 Mar 2024 11:32:06 +0000 Subject: [PATCH 1/6] TW-1456: texts added --- assets/l10n/intl_en.arb | 5 ++++- assets/l10n/intl_fr.arb | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 73047ba7cb..d41296e95d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3010,5 +3010,8 @@ "unselect": "Unselect", "searchContacts": "Search contacts", "tapToAllowAccessToYourMicrophone": "You can enable microphone access in the Settings app to make voice in", - "showInChat": "Show in chat" + "showInChat": "Show in chat", + "phone": "Phone", + "viewProfile": "View profile", + "profileInfo": "Profile info" } diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 2d84a36bd1..e24e199c37 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -3078,4 +3078,7 @@ "@unselect": {}, "searchContacts": "Rechercher des contacts", "@searchContacts": {} + "phone": "Téléphone", + "viewProfile": "Voir le profil", + "profileInfo": "Informations du profil" } From 19aa1110d0fcb4dfd303d134425e31f1f017abf2 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Sat, 16 Mar 2024 11:41:39 +0000 Subject: [PATCH 2/6] TW-1456: Profile info renamed Chat profile info --- .../chat_adaptive_scaffold.dart | 2 +- .../chat_profile_info/chat_profile_info.dart | 12 ++++++------ .../chat_profile_info_navigator.dart | 18 +++++++++--------- .../chat_profile_info_shared.dart | 14 +++++++------- .../chat_profile_info_shared_view.dart | 6 +++--- .../chat_profile_info_view.dart | 6 +++--- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold.dart b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold.dart index 8524f1b52b..19525e500c 100644 --- a/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold.dart +++ b/lib/pages/chat_adaptive_scaffold/chat_adaptive_scaffold.dart @@ -72,7 +72,7 @@ class _RightColumnNavigator extends StatelessWidget { isInStack: isInStack, ); case RightColumnType.profileInfo: - return ProfileInfoNavigator( + return ChatProfileInfoNavigator( onBack: controller.hideRightColumn, roomId: roomId, isInStack: isInStack, diff --git a/lib/pages/chat_profile_info/chat_profile_info.dart b/lib/pages/chat_profile_info/chat_profile_info.dart index 2e772fb556..b68e439815 100644 --- a/lib/pages/chat_profile_info/chat_profile_info.dart +++ b/lib/pages/chat_profile_info/chat_profile_info.dart @@ -13,14 +13,14 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/cupertino.dart'; import 'package:matrix/matrix.dart'; -class ProfileInfo extends StatefulWidget { +class ChatProfileInfo extends StatefulWidget { final VoidCallback? onBack; final String? roomId; final PresentationContact? contact; final bool isInStack; final bool isDraftInfo; - const ProfileInfo({ + const ChatProfileInfo({ super.key, required this.onBack, required this.isInStack, @@ -30,10 +30,10 @@ class ProfileInfo extends StatefulWidget { }); @override - State createState() => ProfileInfoController(); + State createState() => ChatProfileInfoController(); } -class ProfileInfoController extends State { +class ChatProfileInfoController extends State { final _lookupMatchContactInteractor = getIt.get(); @@ -66,7 +66,7 @@ class ProfileInfoController extends State { Navigator.of(context).push( CupertinoPageRoute( builder: (context) { - return ProfileInfoShared( + return ChatProfileInfoShared( roomId: widget.roomId!, closeRightColumn: widget.onBack, ); @@ -90,6 +90,6 @@ class ProfileInfoController extends State { @override Widget build(BuildContext context) { - return ProfileInfoView(this); + return ChatProfileInfoView(this); } } diff --git a/lib/pages/chat_profile_info/chat_profile_info_navigator.dart b/lib/pages/chat_profile_info/chat_profile_info_navigator.dart index 53181b093b..5a3f66159a 100644 --- a/lib/pages/chat_profile_info/chat_profile_info_navigator.dart +++ b/lib/pages/chat_profile_info/chat_profile_info_navigator.dart @@ -5,19 +5,19 @@ import 'package:flutter/cupertino.dart'; import 'package:fluffychat/pages/chat_profile_info/chat_profile_info.dart'; import 'package:fluffychat/presentation/model/presentation_contact.dart'; -class ProfileInfoRoutes { +class ChatProfileInfoRoutes { static const String profileInfo = '/profileInfo'; static const String profileInfoShared = 'profileInfo/shared'; } -class ProfileInfoNavigator extends StatelessWidget { +class ChatProfileInfoNavigator extends StatelessWidget { final VoidCallback? onBack; final String? roomId; final PresentationContact? contact; final bool isInStack; final bool isDraftInfo; - const ProfileInfoNavigator({ + const ChatProfileInfoNavigator({ Key? key, this.onBack, this.roomId, @@ -29,7 +29,7 @@ class ProfileInfoNavigator extends StatelessWidget { @override Widget build(BuildContext context) { if (PlatformInfos.isMobile) { - return ProfileInfo( + return ChatProfileInfo( onBack: onBack, isInStack: isInStack, roomId: roomId, @@ -38,12 +38,12 @@ class ProfileInfoNavigator extends StatelessWidget { ); } return Navigator( - initialRoute: ProfileInfoRoutes.profileInfo, + initialRoute: ChatProfileInfoRoutes.profileInfo, onGenerateRoute: (route) => CupertinoPageRoute( builder: (context) { switch (route.name) { - case ProfileInfoRoutes.profileInfo: - return ProfileInfo( + case ChatProfileInfoRoutes.profileInfo: + return ChatProfileInfo( onBack: onBack, isInStack: isInStack, roomId: roomId, @@ -51,8 +51,8 @@ class ProfileInfoNavigator extends StatelessWidget { isDraftInfo: isDraftInfo, ); - case ProfileInfoRoutes.profileInfoShared: - return ProfileInfoShared( + case ChatProfileInfoRoutes.profileInfoShared: + return ChatProfileInfoShared( roomId: route.arguments as String, ); default: diff --git a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart index e456508360..f54fe75e6b 100644 --- a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart +++ b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart @@ -12,21 +12,21 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -class ProfileInfoShared extends StatefulWidget { +class ChatProfileInfoShared extends StatefulWidget { final String roomId; final VoidCallback? closeRightColumn; - const ProfileInfoShared({ + const ChatProfileInfoShared({ super.key, required this.roomId, this.closeRightColumn, }); @override - State createState() => ProfileInfoSharedController(); + State createState() => ChatProfileInfoSharedController(); } -class ProfileInfoSharedController extends State +class ChatProfileInfoSharedController extends State with HandleVideoDownloadMixin, PlayVideoActionMixin, @@ -67,7 +67,7 @@ class ProfileInfoSharedController extends State ? const SizedBox() : ChatDetailsMediaPage( key: const PageStorageKey( - 'ProfileInfoSharedMedia', + 'ChatProfileInfoSharedMedia', ), controller: mediaListController!, handleDownloadVideoEvent: _handleDownloadAndPlayVideo, @@ -81,7 +81,7 @@ class ProfileInfoSharedController extends State ? const SizedBox() : ChatDetailsLinksPage( key: const PageStorageKey( - 'ProfileInfoSharedLinks', + 'ChatProfileInfoSharedLinks', ), controller: linksListController!, ), @@ -164,7 +164,7 @@ class ProfileInfoSharedController extends State @override Widget build(BuildContext context) { - return ProfileInfoSharedView( + return ChatProfileInfoSharedView( controller: this, ); } diff --git a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared_view.dart b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared_view.dart index 4389a7ef02..f7bdd78d9d 100644 --- a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared_view.dart +++ b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared_view.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; -class ProfileInfoSharedView extends StatelessWidget { - final ProfileInfoSharedController controller; +class ChatProfileInfoSharedView extends StatelessWidget { + final ChatProfileInfoSharedController controller; - const ProfileInfoSharedView({ + const ChatProfileInfoSharedView({ super.key, required this.controller, }); diff --git a/lib/pages/chat_profile_info/chat_profile_info_view.dart b/lib/pages/chat_profile_info/chat_profile_info_view.dart index 1cd4d67027..c0cfcc492d 100644 --- a/lib/pages/chat_profile_info/chat_profile_info_view.dart +++ b/lib/pages/chat_profile_info/chat_profile_info_view.dart @@ -19,10 +19,10 @@ import 'package:fluffychat/pages/chat_profile_info/chat_profile_info.dart'; import 'package:fluffychat/pages/chat_profile_info/chat_profile_info_style.dart'; import 'package:fluffychat/widgets/matrix.dart'; -class ProfileInfoView extends StatelessWidget { - final ProfileInfoController controller; +class ChatProfileInfoView extends StatelessWidget { + final ChatProfileInfoController controller; - const ProfileInfoView( + const ChatProfileInfoView( this.controller, { super.key, }); From d2b84fe65652bf9563a7fb7e7db4f2485e266ebe Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Sat, 16 Mar 2024 12:10:04 +0000 Subject: [PATCH 3/6] TW-1456: profile info views created --- lib/config/go_routes/go_router.dart | 20 ++ .../draft_chat_adaptive_scaffold.dart | 2 +- .../copiable_profile_row.dart | 102 ++++++++++ .../copiable_profile_row_style.dart | 11 ++ .../icon_copiable_profile_row.dart | 21 ++ .../svg_copiable_profile_row.dart | 26 +++ lib/pages/profile_info/profile_info.dart | 27 +++ .../profile_info_body/profile_info_body.dart | 81 ++++++++ .../profile_info_body_view.dart | 180 ++++++++++++++++++ .../profile_info_body_view_style.dart | 29 +++ lib/pages/profile_info/profile_info_view.dart | 64 +++++++ .../profile_info/profile_info_view_style.dart | 9 + .../user_bottom_sheet/user_bottom_sheet.dart | 168 ---------------- .../user_bottom_sheet_view.dart | 137 ------------- .../presence_extension.dart | 32 ++++ 15 files changed, 603 insertions(+), 306 deletions(-) create mode 100644 lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart create mode 100644 lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart create mode 100644 lib/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart create mode 100644 lib/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart create mode 100644 lib/pages/profile_info/profile_info.dart create mode 100644 lib/pages/profile_info/profile_info_body/profile_info_body.dart create mode 100644 lib/pages/profile_info/profile_info_body/profile_info_body_view.dart create mode 100644 lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart create mode 100644 lib/pages/profile_info/profile_info_view.dart create mode 100644 lib/pages/profile_info/profile_info_view_style.dart delete mode 100644 lib/pages/user_bottom_sheet/user_bottom_sheet.dart delete mode 100644 lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index 4d77f4d177..f360dafcc1 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -15,6 +15,7 @@ import 'package:fluffychat/pages/error_page/error_page.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart'; import 'package:fluffychat/pages/login/on_auth_redirect.dart'; import 'package:fluffychat/pages/new_group/new_group_chat_info.dart'; +import 'package:fluffychat/pages/profile_info/profile_info.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile.dart'; import 'package:fluffychat/pages/share/share.dart'; @@ -208,6 +209,25 @@ abstract class AppRoutes { ), ], ), + GoRoute( + path: 'profileinfo/:roomid/:userid', + pageBuilder: (context, state) { + final roomId = state.pathParameters['roomid']; + final userId = state.pathParameters['userid']; + + if (roomId == null || userId == null) { + return defaultPageBuilder(context, const ErrorPage()); + } + + return defaultPageBuilder( + context, + ProfileInfo( + roomId: roomId, + userId: userId, + ), + ); + }, + ), GoRoute( path: 'archive', pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/pages/chat_draft/draft_chat_adaptive_scaffold.dart b/lib/pages/chat_draft/draft_chat_adaptive_scaffold.dart index e0e15ee243..29cf4d800c 100644 --- a/lib/pages/chat_draft/draft_chat_adaptive_scaffold.dart +++ b/lib/pages/chat_draft/draft_chat_adaptive_scaffold.dart @@ -30,7 +30,7 @@ class DraftChatAdaptiveScaffold extends StatelessWidget { }) { switch (type) { case RightColumnType.profileInfo: - return ProfileInfoNavigator( + return ChatProfileInfoNavigator( onBack: controller.hideRightColumn, contact: _contact, isInStack: isInStack, diff --git a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart new file mode 100644 index 0000000000..025a11821f --- /dev/null +++ b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart @@ -0,0 +1,102 @@ +import 'package:fluffychat/pages/chat_profile_info/chat_profile_info_style.dart'; +import 'package:fluffychat/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view_style.dart'; +import 'package:fluffychat/utils/clipboard.dart'; +import 'package:fluffychat/utils/twake_snackbar.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; + +class CopiableProfileRow extends StatelessWidget { + final String caption; + final String copiableText; + final Widget leadingIcon; + + const CopiableProfileRow({ + required this.leadingIcon, + required this.caption, + required this.copiableText, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: ProfileInfoBodyViewStyle.copiableRowPadding, + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: CopiableProfileRowStyle.rippleRadius, + ), + onTap: () { + TwakeClipboard.instance.copyText(copiableText); + TwakeSnackBar.show( + context, + L10n.of(context)!.copiedToClipboard, + ); + }, + child: Padding( + padding: CopiableProfileRowStyle.ripplePadding, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + leadingIcon, + ], + ), + const SizedBox( + width: + CopiableProfileRowStyle.spacerBetweenLeadingIconAndContent, + ), + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: LinagoraSysColors.material() + .surfaceTint + .withOpacity(CopiableProfileRowStyle.borderOpacity), + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + caption, + style: Theme.of(context).textTheme.bodySmall, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + copiableText, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + const Icon( + Icons.content_copy, + size: ChatProfileInfoStyle.copyIconSize, + ), + ], + ), + const SizedBox( + height: CopiableProfileRowStyle.textColumnBottomPadding, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart new file mode 100644 index 0000000000..be923e0827 --- /dev/null +++ b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; + +class CopiableProfileRowStyle { + + static const double spacerBetweenLeadingIconAndContent = 8; + static const double borderOpacity = 0.16; + static const double textColumnBottomPadding = 16; + + static const EdgeInsets ripplePadding = EdgeInsets.all(8.0); + static BorderRadiusGeometry rippleRadius = BorderRadius.circular(8); +} diff --git a/lib/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart b/lib/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart new file mode 100644 index 0000000000..c68d05251a --- /dev/null +++ b/lib/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart @@ -0,0 +1,21 @@ +import 'package:fluffychat/pages/profile_info/copiable_profile_row/copiable_profile_row.dart'; +import 'package:fluffychat/pages/chat_profile_info/chat_profile_info_style.dart'; +import 'package:flutter/material.dart'; + +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; + +class IconCopiableProfileRow extends CopiableProfileRow { + IconCopiableProfileRow({ + required IconData icon, + required super.caption, + required super.copiableText, + Key? key, + }) : super( + key: key, + leadingIcon: Icon( + icon, + size: ChatProfileInfoStyle.iconSize, + color: LinagoraSysColors.material().onSurface, + ), + ); +} diff --git a/lib/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart b/lib/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart new file mode 100644 index 0000000000..ff4d4afcd4 --- /dev/null +++ b/lib/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart @@ -0,0 +1,26 @@ +import 'package:fluffychat/pages/profile_info/copiable_profile_row/copiable_profile_row.dart'; +import 'package:fluffychat/pages/chat_profile_info/chat_profile_info_style.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; + +class SvgCopiableProfileRow extends CopiableProfileRow { + SvgCopiableProfileRow({ + required String leadingIconPath, + required super.caption, + required super.copiableText, + Key? key, + }) : super( + key: key, + leadingIcon: SvgPicture.asset( + leadingIconPath, + width: ChatProfileInfoStyle.iconSize, + height: ChatProfileInfoStyle.iconSize, + colorFilter: ColorFilter.mode( + LinagoraSysColors.material().onSurface, + BlendMode.srcIn, + ), + ), + ); +} diff --git a/lib/pages/profile_info/profile_info.dart b/lib/pages/profile_info/profile_info.dart new file mode 100644 index 0000000000..2bd2a79d38 --- /dev/null +++ b/lib/pages/profile_info/profile_info.dart @@ -0,0 +1,27 @@ +import 'package:fluffychat/pages/profile_info/profile_info_view.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; + +class ProfileInfo extends StatefulWidget { + const ProfileInfo({ + super.key, + required this.roomId, + required this.userId, + }); + + final String roomId; + final String userId; + + @override + State createState() => ProfileInfoState(); +} + +class ProfileInfoState extends State { + Room? get room => Matrix.of(context).client.getRoomById(widget.roomId); + + User? get user => room?.unsafeGetUserFromMemoryOrFallback(widget.userId); + + @override + Widget build(BuildContext context) => ProfileInfoView(this); +} diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body.dart b/lib/pages/profile_info/profile_info_body/profile_info_body.dart new file mode 100644 index 0000000000..fd9cfa864f --- /dev/null +++ b/lib/pages/profile_info/profile_info_body/profile_info_body.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/domain/app_state/contact/lookup_match_contact_state.dart'; +import 'package:fluffychat/domain/usecase/contacts/lookup_match_contact_interactor.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:matrix/matrix.dart'; + +class ProfileInfoBody extends StatefulWidget { + const ProfileInfoBody({ + required this.user, + required this.parentContext, + this.onNewChatOpen, + Key? key, + }) : super(key: key); + + final User? user; + + final BuildContext parentContext; + + final void Function()? onNewChatOpen; + + @override + State createState() => ProfileInfoBodyController(); +} + +class ProfileInfoBodyController extends State { + final _lookupMatchContactInteractor = + getIt.get(); + + StreamSubscription? lookupContactNotifierSub; + + final ValueNotifier> lookupContactNotifier = + ValueNotifier>( + const Right(LookupContactsInitial()), + ); + + User? get user => widget.user; + + BuildContext get parentContext => widget.parentContext; + + bool get isOwnProfile => user?.id == user?.room.client.userID; + + void lookupMatchContactAction() { + if (user == null) return; + lookupContactNotifierSub = _lookupMatchContactInteractor + .execute( + val: user!.id, + ) + .listen( + (event) => lookupContactNotifier.value = event, + ); + } + + void openNewChat(String roomId) { + parentContext.go('/rooms/$roomId'); + if (widget.onNewChatOpen != null) widget.onNewChatOpen!(); + } + + @override + void initState() { + lookupMatchContactAction(); + super.initState(); + } + + @override + void dispose() { + lookupContactNotifier.dispose(); + lookupContactNotifierSub?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => ProfileInfoBodyView(controller: this); +} diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart new file mode 100644 index 0000000000..daa6e33ef3 --- /dev/null +++ b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart @@ -0,0 +1,180 @@ +import 'package:fluffychat/domain/app_state/contact/lookup_match_contact_state.dart'; +import 'package:fluffychat/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart'; +import 'package:fluffychat/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view_style.dart'; +import 'package:fluffychat/resource/image_paths.dart'; +import 'package:fluffychat/utils/dialog/twake_dialog.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/presence_extension.dart'; +import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:matrix/matrix.dart'; + +class ProfileInfoBodyView extends StatelessWidget { + const ProfileInfoBodyView({ + required this.controller, + Key? key, + }) : super(key: key); + final ProfileInfoBodyController controller; + + @override + Widget build(BuildContext context) { + if (controller.user == null) { + return const Center(child: CircularProgressIndicator()); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: ProfileInfoBodyViewStyle.profileInformationsTopPadding, + child: Column( + children: [ + _ProfileInfoHeader(controller.user!), + _ProfileInfoContactRows( + user: controller.user!, + lookupContactNotifier: controller.lookupContactNotifier, + ), + ], + ), + ), + if (!controller.isOwnProfile) ...[ + Divider( + thickness: ProfileInfoBodyViewStyle.bigDividerThickness, + color: LinagoraSysColors.material().surface, + ), + Padding( + padding: ProfileInfoBodyViewStyle.newChatButtonPadding, + child: Row( + children: [ + Expanded( + child: TextButton.icon( + onPressed: () async { + final roomIdResult = + await TwakeDialog.showFutureLoadingDialogFullScreen( + future: () => controller.user!.startDirectChat(), + ); + if (roomIdResult.error != null) return; + + controller.openNewChat(roomIdResult.result!); + }, + icon: const Icon(Icons.chat_outlined), + label: L10n.of(context)?.newChat != null + ? Row( + children: [ + Expanded( + child: Text( + L10n.of(context)!.newChat, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ) + : const SizedBox.shrink(), + ), + ), + ], + ), + ), + ] else + const SizedBox(height: 16), + ], + ); + } +} + +class _ProfileInfoHeader extends StatelessWidget { + const _ProfileInfoHeader(this.user, {Key? key}) : super(key: key); + final User user; + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final presence = client.presences[user.id]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: ProfileInfoBodyViewStyle.avatarPadding, + child: Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + ), + ), + Text( + user.calcDisplayname(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (presence != null) ...[ + const SizedBox(height: 8), + Text( + presence.getLocalizedStatusMessage(context), + style: presence.getPresenceTextStyle(context), + ), + ], + ], + ); + } +} + +class _ProfileInfoContactRows extends StatelessWidget { + const _ProfileInfoContactRows({ + required this.user, + required this.lookupContactNotifier, + Key? key, + }) : super(key: key); + final User user; + final ValueListenable lookupContactNotifier; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const SizedBox(height: 16), + SvgCopiableProfileRow( + leadingIconPath: ImagePaths.icMatrixid, + caption: L10n.of(context)!.matrixId, + copiableText: user.id, + ), + ValueListenableBuilder( + valueListenable: lookupContactNotifier, + // valueListenable: controller.lookupContactNotifier, + builder: (context, contact, child) { + return contact.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is LookupMatchContactSuccess) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (success.contact.email != null) + IconCopiableProfileRow( + icon: Icons.alternate_email, + caption: L10n.of(context)!.email, + copiableText: success.contact.email!, + ), + if (success.contact.phoneNumber != null) + IconCopiableProfileRow( + icon: Icons.call, + caption: L10n.of(context)!.phone, + copiableText: success.contact.phoneNumber!, + ), + ], + ); + } + return const SizedBox.shrink(); + }, + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart b/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart new file mode 100644 index 0000000000..bd47854812 --- /dev/null +++ b/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart @@ -0,0 +1,29 @@ +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; +import 'package:flutter/material.dart'; + +class ProfileInfoBodyViewStyle { + static ResponsiveUtils responsive = getIt.get(); + + static const EdgeInsetsGeometry profileInformationsTopPadding = EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + ); + + static const double bigDividerThickness = 4; + + static const EdgeInsetsGeometry newChatButtonPadding = EdgeInsets.only( + bottom: 16.0, + left: 16.0, + right: 16.0, + ); + + static const EdgeInsetsGeometry copiableRowPadding = EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8, + ); + + static const EdgeInsetsGeometry avatarPadding = + EdgeInsets.symmetric(vertical: 16.0); +} diff --git a/lib/pages/profile_info/profile_info_view.dart b/lib/pages/profile_info/profile_info_view.dart new file mode 100644 index 0000000000..d642086753 --- /dev/null +++ b/lib/pages/profile_info/profile_info_view.dart @@ -0,0 +1,64 @@ +import 'package:fluffychat/pages/profile_info/profile_info.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_view_style.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_state_layer.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; + +class ProfileInfoView extends StatelessWidget { + const ProfileInfoView( + this.controller, { + super.key, + }); + + final ProfileInfoState controller; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: LinagoraSysColors.material().onPrimary, + automaticallyImplyLeading: false, + bottom: PreferredSize( + preferredSize: const Size(double.infinity, 1), + child: Container( + color: LinagoraStateLayer( + LinagoraSysColors.material().surfaceTint, + ).opacityLayer1, + height: 1, + ), + ), + title: Padding( + padding: ProfileInfoViewStyle.navigationAppBarPadding, + child: Row( + children: [ + Padding( + padding: ProfileInfoViewStyle.backIconPadding, + child: IconButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: () => context.pop(), + icon: const Icon(Icons.arrow_back), + ), + ), + Flexible( + child: Text( + L10n.of(context)!.profileInfo, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + body: ProfileInfoBody( + user: controller.user, + parentContext: context, + ), + ); + } +} diff --git a/lib/pages/profile_info/profile_info_view_style.dart b/lib/pages/profile_info/profile_info_view_style.dart new file mode 100644 index 0000000000..bd77134b9b --- /dev/null +++ b/lib/pages/profile_info/profile_info_view_style.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class ProfileInfoViewStyle { + + static const EdgeInsetsGeometry backIconPadding = + EdgeInsets.symmetric(vertical: 8, horizontal: 4); + static const EdgeInsets navigationAppBarPadding = + EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0); +} diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart deleted file mode 100644 index 97a32e8aaa..0000000000 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ /dev/null @@ -1,168 +0,0 @@ -import 'package:fluffychat/utils/dialog/twake_dialog.dart'; -import 'package:fluffychat/utils/twake_snackbar.dart'; -import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; -import 'package:fluffychat/widgets/permission_slider_dialog.dart'; -import '../../widgets/matrix.dart'; -import 'user_bottom_sheet_view.dart'; - -enum UserBottomSheetAction { - report, - mention, - ban, - kick, - unban, - permission, - message, - ignore, -} - -enum ChatMembersStatus { - updated, - notUpdated, -} - -class UserBottomSheet extends StatefulWidget { - final User user; - final Function? onMention; - final BuildContext outerContext; - - const UserBottomSheet({ - Key? key, - required this.user, - required this.outerContext, - this.onMention, - }) : super(key: key); - - @override - UserBottomSheetController createState() => UserBottomSheetController(); -} - -class UserBottomSheetController extends State { - void participantAction(UserBottomSheetAction action) async { - // ignore: prefer_function_declarations_over_variables - final Function askConfirmation = () async => (await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context)!.areYouSure, - okLabel: L10n.of(context)!.yes, - cancelLabel: L10n.of(context)!.no, - ) == - OkCancelResult.ok); - switch (action) { - case UserBottomSheetAction.report: - final event = widget.user; - final score = await showConfirmationDialog( - context: context, - title: L10n.of(context)!.reportUser, - message: L10n.of(context)!.howOffensiveIsThisContent, - cancelLabel: L10n.of(context)!.cancel, - okLabel: L10n.of(context)!.ok, - actions: [ - AlertDialogAction( - key: -100, - label: L10n.of(context)!.extremeOffensive, - ), - AlertDialogAction( - key: -50, - label: L10n.of(context)!.offensive, - ), - AlertDialogAction( - key: 0, - label: L10n.of(context)!.inoffensive, - ), - ], - ); - if (score == null) return; - final reason = await showTextInputDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context)!.whyDoYouWantToReportThis, - okLabel: L10n.of(context)!.ok, - cancelLabel: L10n.of(context)!.cancel, - textFields: [DialogTextField(hintText: L10n.of(context)!.reason)], - ); - if (reason == null || reason.single.isEmpty) return; - final result = await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => Matrix.of(context).client.reportContent( - event.roomId!, - event.eventId, - reason: reason.single, - score: score, - ), - ); - if (result.error != null) return; - TwakeSnackBar.show(context, L10n.of(context)!.contentHasBeenReported); - break; - case UserBottomSheetAction.mention: - Navigator.of(context, rootNavigator: false) - .pop(ChatMembersStatus.notUpdated); - widget.onMention!(); - break; - case UserBottomSheetAction.ban: - if (await askConfirmation()) { - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => widget.user.ban(), - ); - Navigator.of(context, rootNavigator: false) - .pop(ChatMembersStatus.updated); - } - break; - case UserBottomSheetAction.unban: - if (await askConfirmation()) { - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => widget.user.unban(), - ); - Navigator.of(context, rootNavigator: false) - .pop(ChatMembersStatus.updated); - } - break; - case UserBottomSheetAction.kick: - if (await askConfirmation()) { - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => widget.user.kick(), - ); - Navigator.of(context, rootNavigator: false) - .pop(ChatMembersStatus.updated); - } - break; - case UserBottomSheetAction.permission: - final newPermission = await showPermissionChooser( - context, - currentLevel: widget.user.powerLevel, - ); - if (newPermission != null) { - if (newPermission == 100 && await askConfirmation() == false) break; - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => widget.user.setPower(newPermission), - ); - context.pop(ChatMembersStatus.updated); - } - break; - case UserBottomSheetAction.message: - final roomIdResult = - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => widget.user.startDirectChat(), - ); - if (roomIdResult.error != null) return; - context.go('/rooms/${roomIdResult.result!}'); - Navigator.of(context, rootNavigator: false) - .pop(ChatMembersStatus.notUpdated); - break; - case UserBottomSheetAction.ignore: - if (await askConfirmation()) { - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => Matrix.of(context).client.ignoreUser(widget.user.id), - ); - } - } - } - - @override - Widget build(BuildContext context) => UserBottomSheetView(this); -} diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart deleted file mode 100644 index dd82f775a6..0000000000 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:fluffychat/widgets/avatar/avatar_style.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/utils/fluffy_share.dart'; -import 'package:fluffychat/widgets/avatar/avatar.dart'; -import '../../utils/matrix_sdk_extensions/presence_extension.dart'; -import '../../widgets/matrix.dart'; -import 'user_bottom_sheet.dart'; - -class UserBottomSheetView extends StatelessWidget { - final UserBottomSheetController controller; - - const UserBottomSheetView(this.controller, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final user = controller.widget.user; - final client = Matrix.of(context).client; - final presence = client.presences[user.id]; - return SafeArea( - child: Scaffold( - appBar: AppBar( - leading: CloseButton( - onPressed: Navigator.of(context, rootNavigator: false).pop, - ), - title: Text( - user.calcDisplayname(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - actions: [ - if (user.id != client.userID) - Padding( - padding: const EdgeInsets.all(8.0), - child: OutlinedButton.icon( - onPressed: () => controller - .participantAction(UserBottomSheetAction.message), - icon: const Icon(Icons.chat_outlined), - label: Text(L10n.of(context)!.newChat), - ), - ), - ], - ), - body: ListView( - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Avatar( - mxContent: user.avatarUrl, - name: user.calcDisplayname(), - size: AvatarStyle.defaultSize * 2, - fontSize: 24, - ), - ), - Expanded( - child: ListTile( - contentPadding: const EdgeInsets.only(right: 16.0), - title: Text(user.id), - subtitle: presence == null - ? null - : Text(presence.getLocalizedLastActiveAgo(context)), - trailing: IconButton( - icon: Icon(Icons.adaptive.share), - onPressed: () => FluffyShare.share( - user.id, - context, - ), - ), - ), - ), - ], - ), - if (controller.widget.onMention != null) - ListTile( - trailing: const Icon(Icons.alternate_email_outlined), - title: Text(L10n.of(context)!.mention), - onTap: () => - controller.participantAction(UserBottomSheetAction.mention), - ), - if (user.canChangePowerLevel) - ListTile( - title: Text(L10n.of(context)!.setPermissionsLevel), - trailing: const Icon(Icons.edit_attributes_outlined), - onTap: () => controller - .participantAction(UserBottomSheetAction.permission), - ), - if (user.canKick) - ListTile( - title: Text(L10n.of(context)!.kickFromChat), - trailing: const Icon(Icons.exit_to_app_outlined), - onTap: () => - controller.participantAction(UserBottomSheetAction.kick), - ), - if (user.canBan && user.membership != Membership.ban) - ListTile( - title: Text(L10n.of(context)!.banFromChat), - trailing: const Icon(Icons.warning_sharp), - onTap: () => - controller.participantAction(UserBottomSheetAction.ban), - ) - else if (user.canBan && user.membership == Membership.ban) - ListTile( - title: Text(L10n.of(context)!.unbanFromChat), - trailing: const Icon(Icons.warning_outlined), - onTap: () => - controller.participantAction(UserBottomSheetAction.unban), - ), - if (user.id != client.userID && - !client.ignoredUsers.contains(user.id)) - ListTile( - textColor: Theme.of(context).colorScheme.onErrorContainer, - iconColor: Theme.of(context).colorScheme.onErrorContainer, - title: Text(L10n.of(context)!.ignore), - trailing: const Icon(Icons.block), - onTap: () => - controller.participantAction(UserBottomSheetAction.ignore), - ), - if (user.id != client.userID) - ListTile( - textColor: Theme.of(context).colorScheme.error, - iconColor: Theme.of(context).colorScheme.error, - title: Text(L10n.of(context)!.reportUser), - trailing: const Icon(Icons.shield_outlined), - onTap: () => - controller.participantAction(UserBottomSheetAction.report), - ), - ], - ), - ), - ); - } -} diff --git a/lib/utils/matrix_sdk_extensions/presence_extension.dart b/lib/utils/matrix_sdk_extensions/presence_extension.dart index bb6e1069ed..d2cbf9ce4f 100644 --- a/lib/utils/matrix_sdk_extensions/presence_extension.dart +++ b/lib/utils/matrix_sdk_extensions/presence_extension.dart @@ -1,11 +1,17 @@ +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/pages/chat/chat_app_bar_title_style.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; import 'package:matrix/matrix.dart'; import '../date_time_extension.dart'; extension PresenceExtension on CachedPresence { + static ResponsiveUtils responsive = getIt.get(); + String getLocalizedLastActiveAgo(BuildContext context) { final lastActiveTimestamp = this.lastActiveTimestamp; if (lastActiveTimestamp != null) { @@ -37,4 +43,30 @@ extension PresenceExtension on CachedPresence { return Colors.red; } } + + TextStyle? getPresenceTextStyle(BuildContext context) => currentlyActive ?? false + ? _onlineStatusTextStyle(context) + : _offlineStatusTextStyle(context); + + TextStyle? _offlineStatusTextStyle(BuildContext context) => + responsive.isMobile(context) + ? Theme.of(context).textTheme.labelMedium?.copyWith( + color: LinagoraRefColors.material().tertiary[30], + letterSpacing: ChatAppBarTitleStyle.letterSpacingStatusContent, + ) + : Theme.of(context).textTheme.bodySmall?.copyWith( + color: LinagoraRefColors.material().neutral[50], + letterSpacing: ChatAppBarTitleStyle.letterSpacingRoomName, + ); + + TextStyle? _onlineStatusTextStyle(BuildContext context) => + responsive.isMobile(context) + ? Theme.of(context).textTheme.labelMedium?.copyWith( + color: LinagoraRefColors.material().secondary, + letterSpacing: ChatAppBarTitleStyle.letterSpacingStatusContent, + ) + : Theme.of(context).textTheme.bodySmall?.copyWith( + color: LinagoraRefColors.material().secondary, + letterSpacing: ChatAppBarTitleStyle.letterSpacingRoomName, + ); } From 8cc957c6b2f9a27a3caabcd06b20acebe60a98d1 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Sat, 16 Mar 2024 12:14:36 +0000 Subject: [PATCH 4/6] TW-1456: participant list item updated with profile info --- assets/l10n/intl_fr.arb | 2 +- .../chat_details/participant_list_item.dart | 137 ++++++++++++++++-- .../chat_profile_info_shared.dart | 3 +- .../presence_extension.dart | 7 +- 4 files changed, 130 insertions(+), 19 deletions(-) diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index e24e199c37..1b83f09bb9 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -3077,7 +3077,7 @@ "unselect": "Désélectionner", "@unselect": {}, "searchContacts": "Rechercher des contacts", - "@searchContacts": {} + "@searchContacts": {}, "phone": "Téléphone", "viewProfile": "Voir le profil", "profileInfo": "Informations du profil" diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index 05e95a4551..ca21192973 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -1,12 +1,14 @@ -import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; - class ParticipantListItem extends StatelessWidget { final User user; @@ -36,17 +38,124 @@ class ParticipantListItem extends StatelessWidget { opacity: user.membership == Membership.join ? 1 : 0.5, child: ListTile( onTap: () async { - final result = await showAdaptiveBottomSheet( - context: context, - builder: (c) => UserBottomSheet( - user: user, - outerContext: context, - ), - ); - if (result is ChatMembersStatus) { - if (result == ChatMembersStatus.updated) { - onUpdatedMembers?.call(); - } + if (PlatformInfos.isMobile) { + await showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + builder: (c) { + return Container( + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 4, + width: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: LinagoraSysColors.material().outline, + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: TextButton.icon( + onPressed: () { + context.go( + '/rooms/profileinfo/${user.room.id}/${user.id}', + ); + }, + icon: Icon( + Icons.person_search, + color: LinagoraSysColors.material().onSurface, + ), + label: L10n.of(context)?.viewProfile != null + ? Row( + children: [ + Text( + L10n.of(context)!.viewProfile, + style: TextStyle( + color: LinagoraSysColors.material() + .onSurface, + ), + ), + ], + ) + : const SizedBox.shrink(), + ), + ), + ], + ), + // TODO: share button + /*TextButton.icon( + onPressed: () { + // Action pour le premier bouton + }, + icon: Icon(Icons.share), + label: Text('Partager'), + ),*/ + ], + ), + ); + }, + ); + } else { + await showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + contentPadding: const EdgeInsets.all(0), + backgroundColor: LinagoraRefColors.material().primary[100], + surfaceTintColor: Colors.transparent, + content: SizedBox( + width: 448, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: IconButton( + onPressed: () => dialogContext.pop(), + icon: const Icon(Icons.close), + ), + ), + ), + ProfileInfoBody( + user: user, + parentContext: context, + onNewChatOpen: () { + dialogContext.pop(); + }, + ), + ], + ), + ], + ), + ), + ), + ); } }, title: Row( diff --git a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart index f54fe75e6b..6cc4fa04cc 100644 --- a/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart +++ b/lib/pages/chat_profile_info/chat_profile_info_shared/chat_profile_info_shared.dart @@ -23,7 +23,8 @@ class ChatProfileInfoShared extends StatefulWidget { }); @override - State createState() => ChatProfileInfoSharedController(); + State createState() => + ChatProfileInfoSharedController(); } class ChatProfileInfoSharedController extends State diff --git a/lib/utils/matrix_sdk_extensions/presence_extension.dart b/lib/utils/matrix_sdk_extensions/presence_extension.dart index d2cbf9ce4f..fda73527c5 100644 --- a/lib/utils/matrix_sdk_extensions/presence_extension.dart +++ b/lib/utils/matrix_sdk_extensions/presence_extension.dart @@ -44,9 +44,10 @@ extension PresenceExtension on CachedPresence { } } - TextStyle? getPresenceTextStyle(BuildContext context) => currentlyActive ?? false - ? _onlineStatusTextStyle(context) - : _offlineStatusTextStyle(context); + TextStyle? getPresenceTextStyle(BuildContext context) => + currentlyActive ?? false + ? _onlineStatusTextStyle(context) + : _offlineStatusTextStyle(context); TextStyle? _offlineStatusTextStyle(BuildContext context) => responsive.isMobile(context) From 6a41e0c7c3e6cc6d05b94507e39a9710851d55fc Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Mon, 18 Mar 2024 09:58:01 +0000 Subject: [PATCH 5/6] TW-1456: participant list item style created --- assets/l10n/intl_en.arb | 1 + assets/l10n/intl_fr.arb | 1 + lib/config/go_routes/go_router.dart | 4 +- .../chat_details_members_page.dart | 2 +- .../chat_details/participant_list_item.dart | 212 ----------------- .../participant_list_item.dart | 218 ++++++++++++++++++ .../participant_list_item_style.dart | 35 +++ .../copiable_profile_row_style.dart | 1 - .../profile_info_body_view.dart | 2 +- .../profile_info_body_view_style.dart | 3 +- ...ofile_info.dart => profile_info_page.dart} | 8 +- lib/pages/profile_info/profile_info_view.dart | 4 +- .../profile_info/profile_info_view_style.dart | 1 - pubspec.lock | 2 +- pubspec.yaml | 2 +- 15 files changed, 269 insertions(+), 227 deletions(-) delete mode 100644 lib/pages/chat_details/participant_list_item.dart create mode 100644 lib/pages/chat_details/participant_list_item/participant_list_item.dart create mode 100644 lib/pages/chat_details/participant_list_item/participant_list_item_style.dart rename lib/pages/profile_info/{profile_info.dart => profile_info_page.dart} (73%) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index d41296e95d..b528cb9e42 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1764,6 +1764,7 @@ "type": "text", "placeholders": {} }, + "sendMessage": "Send message", "sendOriginal": "Send original", "@sendOriginal": { "type": "text", diff --git a/assets/l10n/intl_fr.arb b/assets/l10n/intl_fr.arb index 1b83f09bb9..017130b09f 100644 --- a/assets/l10n/intl_fr.arb +++ b/assets/l10n/intl_fr.arb @@ -1594,6 +1594,7 @@ "type": "text", "placeholders": {} }, + "sendMessage": "Envoyer un message", "sendAsText": "Envoyer un texte", "@sendAsText": { "type": "text" diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index f360dafcc1..96f8f061e6 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -15,7 +15,7 @@ import 'package:fluffychat/pages/error_page/error_page.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart'; import 'package:fluffychat/pages/login/on_auth_redirect.dart'; import 'package:fluffychat/pages/new_group/new_group_chat_info.dart'; -import 'package:fluffychat/pages/profile_info/profile_info.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_page.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile.dart'; import 'package:fluffychat/pages/share/share.dart'; @@ -221,7 +221,7 @@ abstract class AppRoutes { return defaultPageBuilder( context, - ProfileInfo( + ProfileInfoPage( roomId: roomId, userId: userId, ), diff --git a/lib/pages/chat_details/chat_details_page_view/chat_details_members_page.dart b/lib/pages/chat_details/chat_details_page_view/chat_details_members_page.dart index fc07423a9a..f64e6ce794 100644 --- a/lib/pages/chat_details/chat_details_page_view/chat_details_members_page.dart +++ b/lib/pages/chat_details/chat_details_page_view/chat_details_members_page.dart @@ -1,4 +1,4 @@ -import 'package:fluffychat/pages/chat_details/participant_list_item.dart'; +import 'package:fluffychat/pages/chat_details/participant_list_item/participant_list_item.dart'; import 'package:flutter/material.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart deleted file mode 100644 index ca21192973..0000000000 --- a/lib/pages/chat_details/participant_list_item.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/widgets/avatar/avatar.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; -import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; -import 'package:matrix/matrix.dart'; - -class ParticipantListItem extends StatelessWidget { - final User user; - - final VoidCallback? onUpdatedMembers; - - const ParticipantListItem( - this.user, { - Key? key, - this.onUpdatedMembers, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final membershipBatch = { - Membership.join: '', - Membership.ban: L10n.of(context)!.banned, - Membership.invite: L10n.of(context)!.invited, - Membership.leave: L10n.of(context)!.leftTheChat, - }; - final permissionBatch = user.powerLevel == 100 - ? L10n.of(context)!.admin - : user.powerLevel >= 50 - ? L10n.of(context)!.moderator - : ''; - - return Opacity( - opacity: user.membership == Membership.join ? 1 : 0.5, - child: ListTile( - onTap: () async { - if (PlatformInfos.isMobile) { - await showModalBottomSheet( - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - builder: (c) { - return Container( - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 4, - width: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: LinagoraSysColors.material().outline, - ), - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: TextButton.icon( - onPressed: () { - context.go( - '/rooms/profileinfo/${user.room.id}/${user.id}', - ); - }, - icon: Icon( - Icons.person_search, - color: LinagoraSysColors.material().onSurface, - ), - label: L10n.of(context)?.viewProfile != null - ? Row( - children: [ - Text( - L10n.of(context)!.viewProfile, - style: TextStyle( - color: LinagoraSysColors.material() - .onSurface, - ), - ), - ], - ) - : const SizedBox.shrink(), - ), - ), - ], - ), - // TODO: share button - /*TextButton.icon( - onPressed: () { - // Action pour le premier bouton - }, - icon: Icon(Icons.share), - label: Text('Partager'), - ),*/ - ], - ), - ); - }, - ); - } else { - await showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - contentPadding: const EdgeInsets.all(0), - backgroundColor: LinagoraRefColors.material().primary[100], - surfaceTintColor: Colors.transparent, - content: SizedBox( - width: 448, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - children: [ - Align( - alignment: Alignment.topRight, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: IconButton( - onPressed: () => dialogContext.pop(), - icon: const Icon(Icons.close), - ), - ), - ), - ProfileInfoBody( - user: user, - parentContext: context, - onNewChatOpen: () { - dialogContext.pop(); - }, - ), - ], - ), - ], - ), - ), - ), - ); - } - }, - title: Row( - children: [ - Flexible( - child: Text( - user.calcDisplayname(), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - if (permissionBatch.isNotEmpty) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 2, - ), - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Theme.of(context).colorScheme.primary, - ), - ), - child: Text( - permissionBatch, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - membershipBatch[user.membership]!.isEmpty - ? Container() - : Container( - padding: const EdgeInsets.all(4), - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - borderRadius: BorderRadius.circular(8), - ), - child: - Center(child: Text(membershipBatch[user.membership]!)), - ), - ], - ), - subtitle: Text(user.id), - leading: - Avatar(mxContent: user.avatarUrl, name: user.calcDisplayname()), - ), - ); - } -} diff --git a/lib/pages/chat_details/participant_list_item/participant_list_item.dart b/lib/pages/chat_details/participant_list_item/participant_list_item.dart new file mode 100644 index 0000000000..a3c745014b --- /dev/null +++ b/lib/pages/chat_details/participant_list_item/participant_list_item.dart @@ -0,0 +1,218 @@ +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/pages/chat_details/participant_list_item/participant_list_item_style.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; +import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; +import 'package:linagora_design_flutter/colors/linagora_ref_colors.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:matrix/matrix.dart'; + +class ParticipantListItem extends StatelessWidget { + final User member; + + final VoidCallback? onUpdatedMembers; + + const ParticipantListItem( + this.member, { + Key? key, + this.onUpdatedMembers, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final membershipBatch = { + Membership.join: '', + Membership.ban: L10n.of(context)!.banned, + Membership.invite: L10n.of(context)!.invited, + Membership.leave: L10n.of(context)!.leftTheChat, + }; + final permissionBatch = member.powerLevel == 100 + ? L10n.of(context)!.admin + : member.powerLevel >= 50 + ? L10n.of(context)!.moderator + : ''; + + return Opacity( + opacity: member.membership == Membership.join ? 1 : 0.5, + child: ListTile( + onTap: () async => await _onItemTap(context), + title: Row( + children: [ + Flexible( + child: Text( + member.calcDisplayname(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + if (permissionBatch.isNotEmpty) + Container( + padding: ParticipantListItemStyle.permissionBatchTextPadding, + margin: ParticipantListItemStyle.permissionBatchMargin, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: ParticipantListItemStyle.permissionBatchRadius, + border: Border.all( + color: Theme.of(context).colorScheme.primary, + ), + ), + child: Text( + permissionBatch, + style: TextStyle( + fontSize: + ParticipantListItemStyle.permissionBatchTextFontSize, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + membershipBatch[member.membership]!.isEmpty + ? Container() + : Container( + padding: ParticipantListItemStyle.membershipBatchPadding, + margin: ParticipantListItemStyle.membershipBatchMargin, + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + borderRadius: + ParticipantListItemStyle.membershipBatchRadius, + ), + child: Center( + child: Text(membershipBatch[member.membership]!), + ), + ), + ], + ), + subtitle: Text(member.id), + leading: + Avatar(mxContent: member.avatarUrl, name: member.calcDisplayname()), + ), + ); + } + + Future _onItemTap(BuildContext context) async { + final responsive = getIt.get(); + + if (PlatformInfos.isMobile || responsive.isMobile(context)) { + await _openProfileMenu(context); + } else { + await _openProfileDialog(context); + } + } + + Future _openProfileMenu(BuildContext context) => showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: ParticipantListItemStyle.bottomSheetTopRadius, + topRight: ParticipantListItemStyle.bottomSheetTopRadius, + ), + ), + builder: (c) { + return Container( + padding: ParticipantListItemStyle.bottomSheetContentPadding, + decoration: BoxDecoration( + color: LinagoraRefColors.material().primary[100], + borderRadius: const BorderRadius.only( + topLeft: ParticipantListItemStyle.bottomSheetTopRadius, + topRight: ParticipantListItemStyle.bottomSheetTopRadius, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: ParticipantListItemStyle.dragHandleHeight, + width: ParticipantListItemStyle.dragHandleWidth, + decoration: BoxDecoration( + borderRadius: + ParticipantListItemStyle.dragHandleBorderRadius, + color: LinagoraSysColors.material().outline, + ), + ), + ], + ), + const SizedBox( + height: ParticipantListItemStyle.spacerHeight, + ), + Row( + children: [ + Expanded( + child: TextButton.icon( + onPressed: () { + context.go( + '/rooms/profileinfo/${member.room.id}/${member.id}', + ); + }, + icon: Icon( + Icons.person_search, + color: LinagoraSysColors.material().onSurface, + ), + label: L10n.of(context)?.viewProfile != null + ? Row( + children: [ + Text( + L10n.of(context)!.viewProfile, + style: TextStyle( + color: LinagoraSysColors.material() + .onSurface, + ), + ), + ], + ) + : const SizedBox.shrink(), + ), + ), + ], + ), + ], + ), + ); + }, + ); + + Future _openProfileDialog(BuildContext context) => showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + contentPadding: const EdgeInsets.all(0), + backgroundColor: LinagoraRefColors.material().primary[100], + surfaceTintColor: Colors.transparent, + content: SizedBox( + width: ParticipantListItemStyle.fixedDialogWidth, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: ParticipantListItemStyle.closeButtonPadding, + child: IconButton( + onPressed: () => dialogContext.pop(), + icon: const Icon(Icons.close), + ), + ), + ), + ProfileInfoBody( + user: member, + parentContext: context, + onNewChatOpen: () { + dialogContext.pop(); + }, + ), + ], + ), + ], + ), + ), + ), + ); +} diff --git a/lib/pages/chat_details/participant_list_item/participant_list_item_style.dart b/lib/pages/chat_details/participant_list_item/participant_list_item_style.dart new file mode 100644 index 0000000000..4042aa3edc --- /dev/null +++ b/lib/pages/chat_details/participant_list_item/participant_list_item_style.dart @@ -0,0 +1,35 @@ +import 'package:flutter/widgets.dart'; + +class ParticipantListItemStyle { + // Bottom Sheet + static const Radius bottomSheetTopRadius = Radius.circular(16); + static const Radius bottomSheetContentTopRadius = Radius.circular(16); + static const EdgeInsets bottomSheetContentPadding = EdgeInsets.all(16); + static const double spacerHeight = 16; + + // Bottom Sheet drag handle + static const double dragHandleHeight = 4; + static const double dragHandleWidth = 32; + static BorderRadiusGeometry dragHandleBorderRadius = + BorderRadius.circular(100); + + // Dialog + static const double fixedDialogWidth = 448; + static const EdgeInsets closeButtonPadding = EdgeInsets.all(16); + + // Permission batch + static const EdgeInsets permissionBatchTextPadding = EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ); + static const EdgeInsets permissionBatchMargin = + EdgeInsets.symmetric(horizontal: 8); + static BorderRadiusGeometry permissionBatchRadius = BorderRadius.circular(8); + static const double permissionBatchTextFontSize = 14; + + // Membership batch + static const EdgeInsets membershipBatchPadding = EdgeInsets.all(4); + static const EdgeInsets membershipBatchMargin = + EdgeInsets.symmetric(horizontal: 8); + static BorderRadiusGeometry membershipBatchRadius = BorderRadius.circular(8); +} diff --git a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart index be923e0827..45ec4a67ac 100644 --- a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart +++ b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row_style.dart @@ -1,7 +1,6 @@ import 'package:flutter/widgets.dart'; class CopiableProfileRowStyle { - static const double spacerBetweenLeadingIconAndContent = 8; static const double borderOpacity = 0.16; static const double textColumnBottomPadding = 16; diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart index daa6e33ef3..7e42263988 100644 --- a/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart +++ b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart @@ -69,7 +69,7 @@ class ProfileInfoBodyView extends StatelessWidget { children: [ Expanded( child: Text( - L10n.of(context)!.newChat, + L10n.of(context)!.sendMessage, overflow: TextOverflow.ellipsis, ), ), diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart b/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart index bd47854812..0b7174ba58 100644 --- a/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart +++ b/lib/pages/profile_info/profile_info_body/profile_info_body_view_style.dart @@ -5,7 +5,8 @@ import 'package:flutter/material.dart'; class ProfileInfoBodyViewStyle { static ResponsiveUtils responsive = getIt.get(); - static const EdgeInsetsGeometry profileInformationsTopPadding = EdgeInsets.only( + static const EdgeInsetsGeometry profileInformationsTopPadding = + EdgeInsets.only( top: 16.0, left: 16.0, right: 16.0, diff --git a/lib/pages/profile_info/profile_info.dart b/lib/pages/profile_info/profile_info_page.dart similarity index 73% rename from lib/pages/profile_info/profile_info.dart rename to lib/pages/profile_info/profile_info_page.dart index 2bd2a79d38..8329efce06 100644 --- a/lib/pages/profile_info/profile_info.dart +++ b/lib/pages/profile_info/profile_info_page.dart @@ -3,8 +3,8 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -class ProfileInfo extends StatefulWidget { - const ProfileInfo({ +class ProfileInfoPage extends StatefulWidget { + const ProfileInfoPage({ super.key, required this.roomId, required this.userId, @@ -14,10 +14,10 @@ class ProfileInfo extends StatefulWidget { final String userId; @override - State createState() => ProfileInfoState(); + State createState() => ProfileInfoPageState(); } -class ProfileInfoState extends State { +class ProfileInfoPageState extends State { Room? get room => Matrix.of(context).client.getRoomById(widget.roomId); User? get user => room?.unsafeGetUserFromMemoryOrFallback(widget.userId); diff --git a/lib/pages/profile_info/profile_info_view.dart b/lib/pages/profile_info/profile_info_view.dart index d642086753..47b1943666 100644 --- a/lib/pages/profile_info/profile_info_view.dart +++ b/lib/pages/profile_info/profile_info_view.dart @@ -1,4 +1,4 @@ -import 'package:fluffychat/pages/profile_info/profile_info.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_page.dart'; import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; import 'package:fluffychat/pages/profile_info/profile_info_view_style.dart'; import 'package:flutter/material.dart'; @@ -13,7 +13,7 @@ class ProfileInfoView extends StatelessWidget { super.key, }); - final ProfileInfoState controller; + final ProfileInfoPageState controller; @override Widget build(BuildContext context) { diff --git a/lib/pages/profile_info/profile_info_view_style.dart b/lib/pages/profile_info/profile_info_view_style.dart index bd77134b9b..ecc280f4e2 100644 --- a/lib/pages/profile_info/profile_info_view_style.dart +++ b/lib/pages/profile_info/profile_info_view_style.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; class ProfileInfoViewStyle { - static const EdgeInsetsGeometry backIconPadding = EdgeInsets.symmetric(vertical: 8, horizontal: 4); static const EdgeInsets navigationAppBarPadding = diff --git a/pubspec.lock b/pubspec.lock index baa850bd36..f767bf28a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -3153,4 +3153,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.16.0" \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 85517d6a12..5733999cf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -270,4 +270,4 @@ dependency_overrides: cider: link_template: - tag: https://github.com/linagora/twake-on-matrix/releases/tag/%tag% # initial release link template + tag: https://github.com/linagora/twake-on-matrix/releases/tag/%tag% # initial release link template \ No newline at end of file From 6ae0b91bffecd3212f0d2bbd4403b7aabe5623f9 Mon Sep 17 00:00:00 2001 From: Terence ZAFINDRATAFA Date: Tue, 19 Mar 2024 20:54:34 +0100 Subject: [PATCH 6/6] TW-1456: optionnal duration param added to snackbar --- lib/config/go_routes/go_router.dart | 20 --- .../participant_list_item.dart | 43 +++++-- .../copiable_profile_row.dart | 3 + .../profile_info_body/profile_info_body.dart | 74 +++++++++-- .../profile_info_body_view.dart | 117 +----------------- .../profile_info_contact_rows.dart | 64 ++++++++++ .../profile_info_header.dart | 43 +++++++ lib/pages/profile_info/profile_info_page.dart | 2 + lib/pages/profile_info/profile_info_view.dart | 4 +- lib/utils/twake_snackbar.dart | 11 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- 11 files changed, 232 insertions(+), 151 deletions(-) create mode 100644 lib/pages/profile_info/profile_info_body/profile_info_contact_rows.dart create mode 100644 lib/pages/profile_info/profile_info_body/profile_info_header.dart diff --git a/lib/config/go_routes/go_router.dart b/lib/config/go_routes/go_router.dart index 96f8f061e6..4d77f4d177 100644 --- a/lib/config/go_routes/go_router.dart +++ b/lib/config/go_routes/go_router.dart @@ -15,7 +15,6 @@ import 'package:fluffychat/pages/error_page/error_page.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart'; import 'package:fluffychat/pages/login/on_auth_redirect.dart'; import 'package:fluffychat/pages/new_group/new_group_chat_info.dart'; -import 'package:fluffychat/pages/profile_info/profile_info_page.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_app_language/settings_app_language.dart'; import 'package:fluffychat/pages/settings_dashboard/settings_profile/settings_profile.dart'; import 'package:fluffychat/pages/share/share.dart'; @@ -209,25 +208,6 @@ abstract class AppRoutes { ), ], ), - GoRoute( - path: 'profileinfo/:roomid/:userid', - pageBuilder: (context, state) { - final roomId = state.pathParameters['roomid']; - final userId = state.pathParameters['userid']; - - if (roomId == null || userId == null) { - return defaultPageBuilder(context, const ErrorPage()); - } - - return defaultPageBuilder( - context, - ProfileInfoPage( - roomId: roomId, - userId: userId, - ), - ); - }, - ), GoRoute( path: 'archive', pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/pages/chat_details/participant_list_item/participant_list_item.dart b/lib/pages/chat_details/participant_list_item/participant_list_item.dart index a3c745014b..2408b6c8ff 100644 --- a/lib/pages/chat_details/participant_list_item/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item/participant_list_item.dart @@ -1,9 +1,11 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/pages/chat_details/participant_list_item/participant_list_item_style.dart'; import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_page.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -96,13 +98,42 @@ class ParticipantListItem extends StatelessWidget { Future _onItemTap(BuildContext context) async { final responsive = getIt.get(); - if (PlatformInfos.isMobile || responsive.isMobile(context)) { + if (responsive.isMobile(context)) { await _openProfileMenu(context); } else { await _openProfileDialog(context); } } + Future _openDialogInvite(BuildContext context) async { + if (PlatformInfos.isMobile) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (ctx) => ProfileInfoPage( + roomId: member.room.id, + userId: member.id, + ), + ), + ); + return; + } + await showDialog( + context: context, + barrierDismissible: false, + useSafeArea: false, + useRootNavigator: !PlatformInfos.isMobile, + builder: (dialogContext) { + return ProfileInfoPage( + roomId: member.room.id, + userId: member.id, + onNewChatOpen: () { + dialogContext.pop(); + }, + ); + }, + ); + } + Future _openProfileMenu(BuildContext context) => showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( @@ -111,7 +142,7 @@ class ParticipantListItem extends StatelessWidget { topRight: ParticipantListItemStyle.bottomSheetTopRadius, ), ), - builder: (c) { + builder: (bottomSheetContext) { return Container( padding: ParticipantListItemStyle.bottomSheetContentPadding, decoration: BoxDecoration( @@ -146,10 +177,9 @@ class ParticipantListItem extends StatelessWidget { children: [ Expanded( child: TextButton.icon( - onPressed: () { - context.go( - '/rooms/profileinfo/${member.room.id}/${member.id}', - ); + onPressed: () async { + Navigator.of(bottomSheetContext).pop(); + await _openDialogInvite(context); }, icon: Icon( Icons.person_search, @@ -203,7 +233,6 @@ class ParticipantListItem extends StatelessWidget { ), ProfileInfoBody( user: member, - parentContext: context, onNewChatOpen: () { dialogContext.pop(); }, diff --git a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart index 025a11821f..01394743a1 100644 --- a/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart +++ b/lib/pages/profile_info/copiable_profile_row/copiable_profile_row.dart @@ -9,6 +9,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; class CopiableProfileRow extends StatelessWidget { + static const snackBarDuration = Duration(milliseconds: 500); + final String caption; final String copiableText; final Widget leadingIcon; @@ -31,6 +33,7 @@ class CopiableProfileRow extends StatelessWidget { onTap: () { TwakeClipboard.instance.copyText(copiableText); TwakeSnackBar.show( + duration: snackBarDuration, context, L10n.of(context)!.copiedToClipboard, ); diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body.dart b/lib/pages/profile_info/profile_info_body/profile_info_body.dart index fd9cfa864f..518892ceff 100644 --- a/lib/pages/profile_info/profile_info_body/profile_info_body.dart +++ b/lib/pages/profile_info/profile_info_body/profile_info_body.dart @@ -7,6 +7,10 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/domain/app_state/contact/lookup_match_contact_state.dart'; import 'package:fluffychat/domain/usecase/contacts/lookup_match_contact_interactor.dart'; import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view.dart'; +import 'package:fluffychat/presentation/model/presentation_contact_constant.dart'; +import 'package:fluffychat/presentation/model/search/presentation_search.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -15,15 +19,12 @@ import 'package:matrix/matrix.dart'; class ProfileInfoBody extends StatefulWidget { const ProfileInfoBody({ required this.user, - required this.parentContext, this.onNewChatOpen, Key? key, }) : super(key: key); final User? user; - final BuildContext parentContext; - final void Function()? onNewChatOpen; @override @@ -43,8 +44,6 @@ class ProfileInfoBodyController extends State { User? get user => widget.user; - BuildContext get parentContext => widget.parentContext; - bool get isOwnProfile => user?.id == user?.room.client.userID; void lookupMatchContactAction() { @@ -58,9 +57,68 @@ class ProfileInfoBodyController extends State { ); } - void openNewChat(String roomId) { - parentContext.go('/rooms/$roomId'); - if (widget.onNewChatOpen != null) widget.onNewChatOpen!(); + void openNewChat() { + if (user == null) return; + final roomId = Matrix.of(context).client.getDirectChatFromUserId(user!.id); + + if (roomId == null) { + if (!PlatformInfos.isMobile && widget.onNewChatOpen != null) { + widget.onNewChatOpen!(); + } + + _goToDraftChat( + context: context, + path: "rooms", + contactPresentationSearch: user!.toContactPresentationSearch(), + ); + } else { + if (PlatformInfos.isMobile) { + Navigator.of(context) + .popUntil((route) => route.settings.name == "/rooms/room"); + } else { + if (widget.onNewChatOpen != null) widget.onNewChatOpen!(); + } + + context.go('/rooms/$roomId'); + } + } + + void _goToDraftChat({ + required BuildContext context, + required String path, + required ContactPresentationSearch contactPresentationSearch, + }) { + if (contactPresentationSearch.matrixId != + Matrix.of(context).client.userID) { + Router.neglect( + context, + () => PlatformInfos.isMobile + ? context.push( + '/$path/draftChat', + extra: { + PresentationContactConstant.receiverId: + contactPresentationSearch.matrixId ?? '', + PresentationContactConstant.email: + contactPresentationSearch.email ?? '', + PresentationContactConstant.displayName: + contactPresentationSearch.displayName ?? '', + PresentationContactConstant.status: '', + }, + ) + : context.go( + '/$path/draftChat', + extra: { + PresentationContactConstant.receiverId: + contactPresentationSearch.matrixId ?? '', + PresentationContactConstant.email: + contactPresentationSearch.email ?? '', + PresentationContactConstant.displayName: + contactPresentationSearch.displayName ?? '', + PresentationContactConstant.status: '', + }, + ), + ); + } } @override diff --git a/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart index 7e42263988..997283a56b 100644 --- a/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart +++ b/lib/pages/profile_info/profile_info_body/profile_info_body_view.dart @@ -1,19 +1,11 @@ -import 'package:fluffychat/domain/app_state/contact/lookup_match_contact_state.dart'; -import 'package:fluffychat/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart'; -import 'package:fluffychat/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart'; import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body.dart'; import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view_style.dart'; -import 'package:fluffychat/resource/image_paths.dart'; -import 'package:fluffychat/utils/dialog/twake_dialog.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/presence_extension.dart'; -import 'package:fluffychat/widgets/avatar/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_contact_rows.dart'; +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_header.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; -import 'package:matrix/matrix.dart'; class ProfileInfoBodyView extends StatelessWidget { const ProfileInfoBodyView({ @@ -35,8 +27,8 @@ class ProfileInfoBodyView extends StatelessWidget { padding: ProfileInfoBodyViewStyle.profileInformationsTopPadding, child: Column( children: [ - _ProfileInfoHeader(controller.user!), - _ProfileInfoContactRows( + ProfileInfoHeader(controller.user!), + ProfileInfoContactRows( user: controller.user!, lookupContactNotifier: controller.lookupContactNotifier, ), @@ -54,15 +46,7 @@ class ProfileInfoBodyView extends StatelessWidget { children: [ Expanded( child: TextButton.icon( - onPressed: () async { - final roomIdResult = - await TwakeDialog.showFutureLoadingDialogFullScreen( - future: () => controller.user!.startDirectChat(), - ); - if (roomIdResult.error != null) return; - - controller.openNewChat(roomIdResult.result!); - }, + onPressed: () => controller.openNewChat(), icon: const Icon(Icons.chat_outlined), label: L10n.of(context)?.newChat != null ? Row( @@ -87,94 +71,3 @@ class ProfileInfoBodyView extends StatelessWidget { ); } } - -class _ProfileInfoHeader extends StatelessWidget { - const _ProfileInfoHeader(this.user, {Key? key}) : super(key: key); - final User user; - - @override - Widget build(BuildContext context) { - final client = Matrix.of(context).client; - final presence = client.presences[user.id]; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: ProfileInfoBodyViewStyle.avatarPadding, - child: Avatar( - mxContent: user.avatarUrl, - name: user.calcDisplayname(), - ), - ), - Text( - user.calcDisplayname(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (presence != null) ...[ - const SizedBox(height: 8), - Text( - presence.getLocalizedStatusMessage(context), - style: presence.getPresenceTextStyle(context), - ), - ], - ], - ); - } -} - -class _ProfileInfoContactRows extends StatelessWidget { - const _ProfileInfoContactRows({ - required this.user, - required this.lookupContactNotifier, - Key? key, - }) : super(key: key); - final User user; - final ValueListenable lookupContactNotifier; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const SizedBox(height: 16), - SvgCopiableProfileRow( - leadingIconPath: ImagePaths.icMatrixid, - caption: L10n.of(context)!.matrixId, - copiableText: user.id, - ), - ValueListenableBuilder( - valueListenable: lookupContactNotifier, - // valueListenable: controller.lookupContactNotifier, - builder: (context, contact, child) { - return contact.fold( - (failure) => const SizedBox.shrink(), - (success) { - if (success is LookupMatchContactSuccess) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (success.contact.email != null) - IconCopiableProfileRow( - icon: Icons.alternate_email, - caption: L10n.of(context)!.email, - copiableText: success.contact.email!, - ), - if (success.contact.phoneNumber != null) - IconCopiableProfileRow( - icon: Icons.call, - caption: L10n.of(context)!.phone, - copiableText: success.contact.phoneNumber!, - ), - ], - ); - } - return const SizedBox.shrink(); - }, - ); - }, - ), - ], - ); - } -} diff --git a/lib/pages/profile_info/profile_info_body/profile_info_contact_rows.dart b/lib/pages/profile_info/profile_info_body/profile_info_contact_rows.dart new file mode 100644 index 0000000000..3d7ea59ccb --- /dev/null +++ b/lib/pages/profile_info/profile_info_body/profile_info_contact_rows.dart @@ -0,0 +1,64 @@ +import 'package:fluffychat/domain/app_state/contact/lookup_match_contact_state.dart'; +import 'package:fluffychat/pages/profile_info/copiable_profile_row/icon_copiable_profile_row.dart'; +import 'package:fluffychat/pages/profile_info/copiable_profile_row/svg_copiable_profile_row.dart'; +import 'package:fluffychat/resource/image_paths.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +class ProfileInfoContactRows extends StatelessWidget { + const ProfileInfoContactRows({ + required this.user, + required this.lookupContactNotifier, + Key? key, + }) : super(key: key); + final User user; + final ValueListenable lookupContactNotifier; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const SizedBox(height: 16), + SvgCopiableProfileRow( + leadingIconPath: ImagePaths.icMatrixid, + caption: L10n.of(context)!.matrixId, + copiableText: user.id, + ), + ValueListenableBuilder( + valueListenable: lookupContactNotifier, + // valueListenable: controller.lookupContactNotifier, + builder: (context, contact, child) { + return contact.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is LookupMatchContactSuccess) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (success.contact.email != null) + IconCopiableProfileRow( + icon: Icons.alternate_email, + caption: L10n.of(context)!.email, + copiableText: success.contact.email!, + ), + if (success.contact.phoneNumber != null) + IconCopiableProfileRow( + icon: Icons.call, + caption: L10n.of(context)!.phone, + copiableText: success.contact.phoneNumber!, + ), + ], + ); + } + return const SizedBox.shrink(); + }, + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/profile_info/profile_info_body/profile_info_header.dart b/lib/pages/profile_info/profile_info_body/profile_info_header.dart new file mode 100644 index 0000000000..a3cba044ad --- /dev/null +++ b/lib/pages/profile_info/profile_info_body/profile_info_header.dart @@ -0,0 +1,43 @@ +import 'package:fluffychat/pages/profile_info/profile_info_body/profile_info_body_view_style.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/presence_extension.dart'; +import 'package:fluffychat/widgets/avatar/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +class ProfileInfoHeader extends StatelessWidget { + const ProfileInfoHeader(this.user, {Key? key}) : super(key: key); + final User user; + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final presence = client.presences[user.id]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: ProfileInfoBodyViewStyle.avatarPadding, + child: Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + ), + ), + Text( + user.calcDisplayname(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (presence != null) ...[ + const SizedBox(height: 8), + Text( + presence.getLocalizedStatusMessage(context), + style: presence.getPresenceTextStyle(context), + ), + ], + ], + ); + } +} diff --git a/lib/pages/profile_info/profile_info_page.dart b/lib/pages/profile_info/profile_info_page.dart index 8329efce06..a803dc242d 100644 --- a/lib/pages/profile_info/profile_info_page.dart +++ b/lib/pages/profile_info/profile_info_page.dart @@ -8,10 +8,12 @@ class ProfileInfoPage extends StatefulWidget { super.key, required this.roomId, required this.userId, + this.onNewChatOpen, }); final String roomId; final String userId; + final void Function()? onNewChatOpen; @override State createState() => ProfileInfoPageState(); diff --git a/lib/pages/profile_info/profile_info_view.dart b/lib/pages/profile_info/profile_info_view.dart index 47b1943666..ef19fcde71 100644 --- a/lib/pages/profile_info/profile_info_view.dart +++ b/lib/pages/profile_info/profile_info_view.dart @@ -46,7 +46,7 @@ class ProfileInfoView extends StatelessWidget { ), Flexible( child: Text( - L10n.of(context)!.profileInfo, + L10n.of(context)?.profileInfo ?? "", style: Theme.of(context).textTheme.titleLarge, overflow: TextOverflow.ellipsis, ), @@ -57,7 +57,7 @@ class ProfileInfoView extends StatelessWidget { ), body: ProfileInfoBody( user: controller.user, - parentContext: context, + onNewChatOpen: controller.widget.onNewChatOpen, ), ); } diff --git a/lib/utils/twake_snackbar.dart b/lib/utils/twake_snackbar.dart index a0e6bbc413..dae362bb2e 100644 --- a/lib/utils/twake_snackbar.dart +++ b/lib/utils/twake_snackbar.dart @@ -2,6 +2,8 @@ import 'package:fluffychat/di/global/get_it_initializer.dart'; import 'package:fluffychat/utils/responsive/responsive_utils.dart'; import 'package:flutter/material.dart'; +const Duration _snackBarDefaultDisplayDuration = Duration(milliseconds: 4000); + class TwakeSnackBarStyle { static ResponsiveUtils responsiveUtils = getIt.get(); @@ -21,11 +23,18 @@ class TwakeSnackBarStyle { } class TwakeSnackBar { - static void show(BuildContext context, String message) { + static void show( + BuildContext context, + String message, { + Duration duration = _snackBarDefaultDisplayDuration, + }) { + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar( SnackBar( width: TwakeSnackBarStyle.widthSnackBar(context), padding: TwakeSnackBarStyle.snackBarPadding, + duration: duration, content: Text( message, style: Theme.of(context).textTheme.bodyMedium?.copyWith( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f2af6f2ea8..16840a23f3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -83,4 +83,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) -} +} \ No newline at end of file