From 16cf4e5e6c85381974f24359a037cee5a4871746 Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 26 Jul 2024 14:52:41 +0200 Subject: [PATCH 01/13] refactor: Design polishment and better user viewer --- lib/pages/chat_details/chat_details_view.dart | 54 +++------ lib/pages/new_group/new_group_view.dart | 4 - lib/pages/new_space/new_space_view.dart | 4 - lib/pages/settings/settings_view.dart | 61 +++------- .../settings_3pid/settings_3pid_view.dart | 2 +- .../settings_chat/settings_chat_view.dart | 10 +- .../settings_emotes/settings_emotes_view.dart | 2 +- .../settings_security_view.dart | 1 - .../settings_style/settings_style_view.dart | 3 - .../user_bottom_sheet/user_bottom_sheet.dart | 39 +++++++ .../user_bottom_sheet_view.dart | 106 ++++++++++-------- 11 files changed, 134 insertions(+), 152 deletions(-) diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 4e19e43ca..3c17112a5 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -85,33 +85,16 @@ class ChatDetailsView extends StatelessWidget { padding: const EdgeInsets.all(32.0), child: Stack( children: [ - Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: Theme.of(context) - .appBarTheme - .shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Hero( - tag: controller - .widget.embeddedCloseButton != - null - ? 'embedded_content_banner' - : 'content_banner', - child: Avatar( - mxContent: room.avatar, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + Hero( + tag: + controller.widget.embeddedCloseButton != + null + ? 'embedded_content_banner' + : 'content_banner', + child: Avatar( + mxContent: room.avatar, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), if (!room.isDirectChat && @@ -170,7 +153,7 @@ class ChatDetailsView extends StatelessWidget { : displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ), TextButton.icon( @@ -202,10 +185,7 @@ class ChatDetailsView extends StatelessWidget { ), ], ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), if (!room.canChangeStateEvent(EventTypes.RoomTopic)) ListTile( title: Text( @@ -261,10 +241,7 @@ class ChatDetailsView extends StatelessWidget { ), ), const SizedBox(height: 16), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( leading: CircleAvatar( backgroundColor: @@ -316,10 +293,7 @@ class ChatDetailsView extends StatelessWidget { onTap: () => context .push('/rooms/${room.id}/details/permissions'), ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.countParticipants( diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index a83497d7b..df2a4ac49 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -102,10 +102,6 @@ class NewGroupView extends StatelessWidget { child: SizedBox( width: double.infinity, child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), onPressed: controller.loading ? null : controller.submitAction, child: controller.loading diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index e90c7a84e..2f46e5e73 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -74,10 +74,6 @@ class NewSpaceView extends StatelessWidget { child: SizedBox( width: double.infinity, child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), onPressed: controller.loading ? null : controller.submitAction, child: controller.loading diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index b1d60f142..b2293f4b2 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -28,13 +28,6 @@ class SettingsView extends StatelessWidget { ), ), title: Text(L10n.of(context)!.settings), - actions: [ - TextButton.icon( - onPressed: controller.logoutAction, - label: Text(L10n.of(context)!.logout), - icon: const Icon(Icons.logout_outlined), - ), - ], ), body: ListTileTheme( iconColor: Theme.of(context).colorScheme.onSurface, @@ -55,32 +48,17 @@ class SettingsView extends StatelessWidget { padding: const EdgeInsets.all(32.0), child: Stack( children: [ - Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: - Theme.of(context).appBarTheme.shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Avatar( - mxContent: profile?.avatarUrl, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + Avatar( + mxContent: profile?.avatarUrl, + name: displayname, + size: Avatar.defaultSize * 2.5, ), if (profile != null) Positioned( bottom: 0, right: 0, child: FloatingActionButton.small( + elevation: 2, onPressed: controller.setAvatarAction, heroTag: null, child: const Icon(Icons.camera_alt_outlined), @@ -108,7 +86,9 @@ class SettingsView extends StatelessWidget { displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle( + fontSize: 18, + ), ), ), TextButton.icon( @@ -135,10 +115,7 @@ class SettingsView extends StatelessWidget { ); }, ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), if (showChatBackupBanner == null) ListTile( leading: const Icon(Icons.backup_outlined), @@ -154,60 +131,54 @@ class SettingsView extends StatelessWidget { onChanged: controller.firstRunBootstrapAction, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( leading: const Icon(Icons.format_paint_outlined), title: Text(L10n.of(context)!.changeTheme), onTap: () => context.go('/rooms/settings/style'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.notifications_outlined), title: Text(L10n.of(context)!.notifications), onTap: () => context.go('/rooms/settings/notifications'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.devices_outlined), title: Text(L10n.of(context)!.devices), onTap: () => context.go('/rooms/settings/devices'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.forum_outlined), title: Text(L10n.of(context)!.chat), onTap: () => context.go('/rooms/settings/chat'), - trailing: const Icon(Icons.chevron_right_outlined), ), ListTile( leading: const Icon(Icons.shield_outlined), title: Text(L10n.of(context)!.security), onTap: () => context.go('/rooms/settings/security'), - trailing: const Icon(Icons.chevron_right_outlined), - ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, ), + Divider(color: Theme.of(context).dividerColor), ListTile( leading: const Icon(Icons.help_outline_outlined), title: Text(L10n.of(context)!.help), onTap: () => launchUrlString(AppConfig.supportUrl), - trailing: const Icon(Icons.open_in_new_outlined), ), ListTile( leading: const Icon(Icons.shield_sharp), title: Text(L10n.of(context)!.privacy), onTap: () => launchUrlString(AppConfig.privacyUrl), - trailing: const Icon(Icons.open_in_new_outlined), ), ListTile( leading: const Icon(Icons.info_outline_rounded), title: Text(L10n.of(context)!.about), onTap: () => PlatformInfos.showDialog(context), - trailing: const Icon(Icons.chevron_right_outlined), + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + leading: const Icon(Icons.logout_outlined), + title: Text(L10n.of(context)!.logout), + onTap: controller.logoutAction, ), ], ), diff --git a/lib/pages/settings_3pid/settings_3pid_view.dart b/lib/pages/settings_3pid/settings_3pid_view.dart index 49f8b4489..4c5377c64 100644 --- a/lib/pages/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_3pid/settings_3pid_view.dart @@ -69,7 +69,7 @@ class Settings3PidView extends StatelessWidget { .withTheseAddressesRecoveryDescription, ), ), - const Divider(height: 1), + const Divider(), Expanded( child: ListView.builder( itemCount: identifier.length, diff --git a/lib/pages/settings_chat/settings_chat_view.dart b/lib/pages/settings_chat/settings_chat_view.dart index c3abe990e..01cc7af29 100644 --- a/lib/pages/settings_chat/settings_chat_view.dart +++ b/lib/pages/settings_chat/settings_chat_view.dart @@ -71,10 +71,7 @@ class SettingsChatView extends StatelessWidget { storeKey: SettingKeys.swipeRightToLeftToReply, defaultValue: AppConfig.swipeRightToLeftToReply, ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.customEmojisAndStickers, @@ -93,10 +90,7 @@ class SettingsChatView extends StatelessWidget { child: Icon(Icons.chevron_right_outlined), ), ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), ListTile( title: Text( L10n.of(context)!.calls, diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index 7e72cd464..be1e36f92 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -117,7 +117,7 @@ class EmotesSettingsView extends StatelessWidget { onChanged: controller.setIsGloballyActive, ), if (!controller.readonly || controller.room != null) - const Divider(thickness: 1), + const Divider(), imageKeys.isEmpty ? Center( child: Padding( diff --git a/lib/pages/settings_security/settings_security_view.dart b/lib/pages/settings_security/settings_security_view.dart index 49b39d750..6ff7e1696 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -86,7 +86,6 @@ class SettingsSecurityView extends StatelessWidget { ), }, Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( diff --git a/lib/pages/settings_style/settings_style_view.dart b/lib/pages/settings_style/settings_style_view.dart index 86f48fe87..1a07dd2bc 100644 --- a/lib/pages/settings_style/settings_style_view.dart +++ b/lib/pages/settings_style/settings_style_view.dart @@ -136,7 +136,6 @@ class SettingsStyleView extends StatelessWidget { ), const SizedBox(height: 8), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( @@ -167,7 +166,6 @@ class SettingsStyleView extends StatelessWidget { onChanged: controller.switchTheme, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( @@ -192,7 +190,6 @@ class SettingsStyleView extends StatelessWidget { defaultValue: AppConfig.separateChatTypes, ), Divider( - height: 1, color: Theme.of(context).dividerColor, ), ListTile( diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 527915072..1ae173341 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -226,6 +226,45 @@ class UserBottomSheetController extends State { } } + bool isSending = false; + + Object? sendError; + + final TextEditingController sendController = TextEditingController(); + + void sendAction([_]) async { + final userId = widget.user?.id ?? widget.profile?.userId; + final client = Matrix.of(context).client; + if (userId == null) throw ('user or profile must not be null!'); + + final input = sendController.text.trim(); + if (input.isEmpty) return; + + setState(() { + isSending = true; + sendError = null; + }); + try { + final roomId = await client.startDirectChat(userId); + if (!mounted) return; + final room = client.getRoomById(roomId); + if (room == null) { + throw ('DM Room found or created but room not found in client'); + } + await room.sendTextEvent(input); + setState(() { + isSending = false; + sendController.clear(); + }); + } catch (e, s) { + Logs().d('Unable to send message', e, s); + setState(() { + isSending = false; + sendError = e; + }); + } + } + void knockAccept() async { final user = widget.user!; final result = await showFutureLoadingDialog( diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 9e72a4a3f..c6d3b5979 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/presence_builder.dart'; @@ -29,6 +30,7 @@ class UserBottomSheetView extends StatelessWidget { final client = Matrix.of(controller.widget.outerContext).client; final profileSearchError = controller.widget.profileSearchError; + final dmRoomId = client.getDirectChatFromUserId(userId); return SafeArea( child: Scaffold( appBar: AppBar( @@ -90,19 +92,19 @@ class UserBottomSheetView extends StatelessWidget { ), ], ), - actions: [ - if (userId != client.userID && - !client.ignoredUsers.contains(userId)) - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: IconButton( - icon: const Icon(Icons.block_outlined), - tooltip: L10n.of(context)!.block, - onPressed: () => controller - .participantAction(UserBottomSheetAction.ignore), - ), - ), - ], + actions: dmRoomId == null + ? null + : [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FloatingActionButton.small( + elevation: 0, + onPressed: () => controller + .participantAction(UserBottomSheetAction.message), + child: const Icon(Icons.chat_outlined), + ), + ), + ], ), body: StreamBuilder( stream: user?.room.client.onSync.stream.where( @@ -169,25 +171,10 @@ class UserBottomSheetView extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(16.0), - child: Material( - elevation: Theme.of(context) - .appBarTheme - .scrolledUnderElevation ?? - 4, - shadowColor: Theme.of(context).appBarTheme.shadowColor, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular( - Avatar.defaultSize * 2.5, - ), - ), - child: Avatar( - mxContent: avatarUrl, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + child: Avatar( + mxContent: avatarUrl, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), Expanded( @@ -212,7 +199,7 @@ class UserBottomSheetView extends StatelessWidget { displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ), TextButton.icon( @@ -247,16 +234,35 @@ class UserBottomSheetView extends StatelessWidget { horizontal: 16.0, vertical: 8.0, ), - child: ElevatedButton.icon( - onPressed: () => controller - .participantAction(UserBottomSheetAction.message), - icon: const Icon(Icons.forum_outlined), - label: Text( - controller.widget.user == null - ? L10n.of(context)!.startConversation - : L10n.of(context)!.sendAMessage, - ), - ), + child: dmRoomId == null + ? ElevatedButton.icon( + onPressed: () => controller.participantAction( + UserBottomSheetAction.message, + ), + icon: const Icon(Icons.chat_outlined), + label: Text(L10n.of(context)!.startConversation), + ) + : TextField( + controller: controller.sendController, + readOnly: controller.isSending, + onSubmitted: controller.sendAction, + minLines: 1, + maxLines: 1, + textInputAction: TextInputAction.send, + decoration: InputDecoration( + errorText: controller.sendError + ?.toLocalizedString(context), + hintText: L10n.of(context)!.sendMessages, + suffixIcon: controller.isSending + ? const CircularProgressIndicator.adaptive( + strokeWidth: 2, + ) + : IconButton( + icon: const Icon(Icons.send_outlined), + onPressed: controller.sendAction, + ), + ), + ), ), PresenceBuilder( userId: userId, @@ -334,8 +340,8 @@ class UserBottomSheetView extends StatelessWidget { ), ), ), - Divider(color: Theme.of(context).dividerColor), ], + Divider(color: Theme.of(context).dividerColor), if (user != null && user.canKick) ListTile( textColor: Theme.of(context).colorScheme.error, @@ -370,7 +376,7 @@ class UserBottomSheetView extends StatelessWidget { textColor: Theme.of(context).colorScheme.onErrorContainer, iconColor: Theme.of(context).colorScheme.onErrorContainer, title: Text(L10n.of(context)!.reportUser), - leading: const Icon(Icons.report_outlined), + leading: const Icon(Icons.gavel_outlined), onTap: () => controller .participantAction(UserBottomSheetAction.report), ), @@ -385,6 +391,16 @@ class UserBottomSheetView extends StatelessWidget { style: const TextStyle(color: Colors.orange), ), ), + if (userId != client.userID && + !client.ignoredUsers.contains(userId)) + ListTile( + textColor: Theme.of(context).colorScheme.onErrorContainer, + iconColor: Theme.of(context).colorScheme.onErrorContainer, + leading: const Icon(Icons.block_outlined), + title: Text(L10n.of(context)!.block), + onTap: () => controller + .participantAction(UserBottomSheetAction.ignore), + ), ], ); }, From 39a66f678676acb67b1abf79a73fabd64b6c9d83 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 16:20:13 +0200 Subject: [PATCH 02/13] chore: Follow up user bottom sheet --- .../user_bottom_sheet/user_bottom_sheet_view.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index c6d3b5979..fbdcb52ec 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -96,7 +96,7 @@ class UserBottomSheetView extends StatelessWidget { ? null : [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 8), child: FloatingActionButton.small( elevation: 0, onPressed: () => controller @@ -253,10 +253,17 @@ class UserBottomSheetView extends StatelessWidget { errorText: controller.sendError ?.toLocalizedString(context), hintText: L10n.of(context)!.sendMessages, - suffixIcon: controller.isSending - ? const CircularProgressIndicator.adaptive( - strokeWidth: 2, + suffix: controller.isSending + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), ) + : null, + suffixIcon: controller.isSending + ? null : IconButton( icon: const Icon(Icons.send_outlined), onPressed: controller.sendAction, From 22cfdd7689c27aeb1f326aadef52571d2a4bbf5b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 16:50:41 +0200 Subject: [PATCH 03/13] chore: Follow up user bottom sheet --- .../user_bottom_sheet_view.dart | 131 ++++++++---------- 1 file changed, 55 insertions(+), 76 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index fbdcb52ec..8630faba0 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -38,60 +38,7 @@ class UserBottomSheetView extends StatelessWidget { onPressed: Navigator.of(context, rootNavigator: false).pop, ), centerTitle: false, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(displayname), - PresenceBuilder( - userId: userId, - client: client, - builder: (context, presence) { - if (presence == null || - (presence.presence == PresenceType.offline && - presence.lastActiveTimestamp == null)) { - return const SizedBox.shrink(); - } - - final dotColor = presence.presence.isOnline - ? Colors.green - : presence.presence.isUnavailable - ? Colors.orange - : Colors.grey; - - final lastActiveTimestamp = presence.lastActiveTimestamp; - - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 8, - height: 8, - margin: const EdgeInsets.only(right: 8), - decoration: BoxDecoration( - color: dotColor, - borderRadius: BorderRadius.circular(16), - ), - ), - if (presence.currentlyActive == true) - Text( - L10n.of(context)!.currentlyActive, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ) - else if (lastActiveTimestamp != null) - Text( - L10n.of(context)!.lastActiveAgo( - lastActiveTimestamp.localizedTimeShort(context), - ), - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ); - }, - ), - ], - ), + title: Text(displayname), actions: dmRoomId == null ? null : [ @@ -182,26 +129,6 @@ class UserBottomSheetView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextButton.icon( - onPressed: () => FluffyShare.share( - 'https://matrix.to/#/$userId', - context, - ), - icon: Icon( - Icons.adaptive.share_outlined, - size: 16, - ), - style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface, - ), - label: Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 18), - ), - ), TextButton.icon( onPressed: () => FluffyShare.share( userId, @@ -214,15 +141,67 @@ class UserBottomSheetView extends StatelessWidget { ), style: TextButton.styleFrom( foregroundColor: - Theme.of(context).colorScheme.secondary, + Theme.of(context).colorScheme.onSurface, ), label: Text( userId, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 12), ), ), + PresenceBuilder( + userId: userId, + client: client, + builder: (context, presence) { + if (presence == null || + (presence.presence == PresenceType.offline && + presence.lastActiveTimestamp == null)) { + return const SizedBox.shrink(); + } + + final dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + + final lastActiveTimestamp = + presence.lastActiveTimestamp; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 16), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + ), + ), + const SizedBox(width: 12), + if (presence.currentlyActive == true) + Text( + L10n.of(context)!.currentlyActive, + overflow: TextOverflow.ellipsis, + style: + Theme.of(context).textTheme.bodySmall, + ) + else if (lastActiveTimestamp != null) + Text( + L10n.of(context)!.lastActiveAgo( + lastActiveTimestamp + .localizedTimeShort(context), + ), + overflow: TextOverflow.ellipsis, + style: + Theme.of(context).textTheme.bodySmall, + ), + ], + ); + }, + ), ], ), ), From d71d633ccef6e86b215a011dddb7f826b2ce34a8 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 17:32:36 +0200 Subject: [PATCH 04/13] chore: nicer bottom sheets --- .../user_bottom_sheet/user_bottom_sheet.dart | 2 +- .../user_bottom_sheet_view.dart | 46 +++++++++---------- lib/utils/adaptive_bottom_sheet.dart | 7 +-- lib/widgets/public_room_bottom_sheet.dart | 10 ++-- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 1ae173341..45be5c975 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -234,7 +234,7 @@ class UserBottomSheetController extends State { void sendAction([_]) async { final userId = widget.user?.id ?? widget.profile?.userId; - final client = Matrix.of(context).client; + final client = Matrix.of(widget.outerContext).client; if (userId == null) throw ('user or profile must not be null!'); final input = sendController.text.trim(); diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 8630faba0..01035bb77 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -207,6 +207,29 @@ class UserBottomSheetView extends StatelessWidget { ), ], ), + PresenceBuilder( + userId: userId, + client: client, + builder: (context, presence) { + final status = presence?.statusMsg; + if (status == null || status.isEmpty) { + return const SizedBox.shrink(); + } + return ListTile( + title: SelectableLinkify( + text: status, + style: const TextStyle(fontSize: 16), + options: const LinkifyOptions(humanize: false), + linkStyle: const TextStyle( + color: Colors.blueAccent, + decorationColor: Colors.blueAccent, + ), + onOpen: (url) => + UrlLauncher(context, url.url).launchUrl(), + ), + ); + }, + ), if (userId != client.userID) Padding( padding: const EdgeInsets.symmetric( @@ -250,29 +273,6 @@ class UserBottomSheetView extends StatelessWidget { ), ), ), - PresenceBuilder( - userId: userId, - client: client, - builder: (context, presence) { - final status = presence?.statusMsg; - if (status == null || status.isEmpty) { - return const SizedBox.shrink(); - } - return ListTile( - title: SelectableLinkify( - text: status, - style: const TextStyle(fontSize: 16), - options: const LinkifyOptions(humanize: false), - linkStyle: const TextStyle( - color: Colors.blueAccent, - decorationColor: Colors.blueAccent, - ), - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), - ), - ); - }, - ), if (controller.widget.onMention != null) ListTile( leading: const Icon(Icons.alternate_email_outlined), diff --git a/lib/utils/adaptive_bottom_sheet.dart b/lib/utils/adaptive_bottom_sheet.dart index cfe487ffd..d21ca6c44 100644 --- a/lib/utils/adaptive_bottom_sheet.dart +++ b/lib/utils/adaptive_bottom_sheet.dart @@ -8,18 +8,19 @@ Future showAdaptiveBottomSheet({ required Widget Function(BuildContext) builder, bool isDismissible = true, bool isScrollControlled = true, - double maxHeight = 480.0, + double maxHeight = 512, + bool useRootNavigator = true, }) => showModalBottomSheet( context: context, builder: builder, // this sadly is ugly on desktops but otherwise breaks `.of(context)` calls - useRootNavigator: false, + useRootNavigator: useRootNavigator, isDismissible: isDismissible, isScrollControlled: isScrollControlled, constraints: BoxConstraints( maxHeight: maxHeight, - maxWidth: FluffyThemes.columnWidth * 1.5, + maxWidth: FluffyThemes.columnWidth * 1.25, ), clipBehavior: Clip.hardEdge, shape: const RoundedRectangleBorder( diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 8bd354ef5..4f5806c2d 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -64,17 +64,17 @@ class PublicRoomBottomSheet extends StatelessWidget { bool _testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias; - Future _search(BuildContext context) async { + Future _search() async { final chunk = this.chunk; if (chunk != null) return chunk; - final query = await Matrix.of(context).client.queryPublicRooms( + final query = await Matrix.of(outerContext).client.queryPublicRooms( server: roomAlias!.domain, filter: PublicRoomQueryFilter( genericSearchTerm: roomAlias, ), ); if (!query.chunk.any(_testRoom)) { - throw (L10n.of(context)!.noRoomsFound); + throw (L10n.of(outerContext)!.noRoomsFound); } return query.chunk.firstWhere(_testRoom); } @@ -108,7 +108,7 @@ class PublicRoomBottomSheet extends StatelessWidget { ], ), body: FutureBuilder( - future: _search(context), + future: _search(), builder: (context, snapshot) { final profile = snapshot.data; return ListView( @@ -142,7 +142,7 @@ class PublicRoomBottomSheet extends StatelessWidget { onPressed: () => _joinRoom(context), label: Text( chunk?.joinRule == 'knock' && - Matrix.of(context) + Matrix.of(outerContext) .client .getRoomById(chunk!.roomId) == null From b20cee34d4275e97ba680d30b52b95d6e6e6c2f6 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 17:57:30 +0200 Subject: [PATCH 05/13] chore: Follow up nicer max width pages --- lib/pages/chat_list/chat_list.dart | 7 +-- lib/widgets/layouts/max_width_body.dart | 62 ++++++++++++------------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 63cb35571..99d31bf58 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -716,9 +716,10 @@ class ChatListController extends State isDestructiveAction: true, ); if (confirmed == OkCancelResult.cancel) return; - if (!mounted) { - await showFutureLoadingDialog(context: context, future: room.leave); - } + if (!mounted) return; + + await showFutureLoadingDialog(context: context, future: room.leave); + return; } } diff --git a/lib/widgets/layouts/max_width_body.dart b/lib/widgets/layouts/max_width_body.dart index e9499d31e..97c340231 100644 --- a/lib/widgets/layouts/max_width_body.dart +++ b/lib/widgets/layouts/max_width_body.dart @@ -1,20 +1,17 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; class MaxWidthBody extends StatelessWidget { - final Widget? child; + final Widget child; final double maxWidth; - final bool withFrame; final bool withScrolling; final EdgeInsets? innerPadding; const MaxWidthBody({ - this.child, + required this.child, this.maxWidth = 600, - this.withFrame = true, this.withScrolling = true, this.innerPadding, super.key, @@ -24,36 +21,35 @@ class MaxWidthBody extends StatelessWidget { return SafeArea( child: LayoutBuilder( builder: (context, constraints) { - final paddingVal = max(0, (constraints.maxWidth - maxWidth) / 2); - final hasPadding = paddingVal > 0; - final padding = EdgeInsets.symmetric( - vertical: hasPadding ? 32 : 0, - horizontal: max(0, (constraints.maxWidth - maxWidth) / 2), - ); - final childWithPadding = Padding( - padding: padding, - child: withFrame && hasPadding - ? Material( - elevation: - Theme.of(context).appBarTheme.scrolledUnderElevation ?? - 4, - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - shadowColor: Theme.of(context).appBarTheme.shadowColor, - child: child, - ) - : child, - ); - if (!withScrolling) { - return Padding( - padding: innerPadding ?? EdgeInsets.zero, - child: childWithPadding, - ); - } + const desiredWidth = FluffyThemes.columnWidth * 1.5; + final body = constraints.maxWidth <= desiredWidth + ? child + : Container( + alignment: Alignment.topCenter, + padding: const EdgeInsets.all(32), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + ), + child: Material( + elevation: Theme.of(context) + .appBarTheme + .scrolledUnderElevation ?? + 4, + clipBehavior: Clip.hardEdge, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + shadowColor: Theme.of(context).appBarTheme.shadowColor, + child: child, + ), + ), + ); + if (!withScrolling) return body; + return SingleChildScrollView( padding: innerPadding, physics: const ScrollPhysics(), - child: childWithPadding, + child: body, ); }, ), From 8128c960dc5523a99049c84d58f1b45e7a3848d5 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 18:07:54 +0200 Subject: [PATCH 06/13] chore: Follow up pinned events --- lib/pages/chat/chat_app_bar_list_tile.dart | 61 ++++++++++++---------- lib/pages/chat/chat_view.dart | 6 +-- lib/pages/chat/pinned_events.dart | 4 +- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/lib/pages/chat/chat_app_bar_list_tile.dart b/lib/pages/chat/chat_app_bar_list_tile.dart index 1e0ec8259..259144251 100644 --- a/lib/pages/chat/chat_app_bar_list_tile.dart +++ b/lib/pages/chat/chat_app_bar_list_tile.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/url_launcher.dart'; class ChatAppBarListTile extends StatelessWidget { @@ -11,6 +10,8 @@ class ChatAppBarListTile extends StatelessWidget { final Widget? trailing; final void Function()? onTap; + static const double fixedHeight = 40.0; + const ChatAppBarListTile({ super.key, this.leading, @@ -23,38 +24,40 @@ class ChatAppBarListTile extends StatelessWidget { Widget build(BuildContext context) { final leading = this.leading; final trailing = this.trailing; - final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; - return InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) leading, - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Linkify( - text: title, - options: const LinkifyOptions(humanize: false), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + return SizedBox( + height: fixedHeight, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) leading, + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: Linkify( + text: title, + options: const LinkifyOptions(humanize: false), + maxLines: 1, overflow: TextOverflow.ellipsis, - fontSize: fontSize, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + overflow: TextOverflow.ellipsis, + fontSize: 14, + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 14, + decoration: TextDecoration.underline, + decorationColor: + Theme.of(context).colorScheme.onSurfaceVariant, + ), + onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: fontSize, - decoration: TextDecoration.underline, - decorationColor: - Theme.of(context).colorScheme.onSurfaceVariant, - ), - onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), ), - ), - if (trailing != null) trailing, - ], + if (trailing != null) trailing, + ], + ), ), ); } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 80a84fa41..422175f0f 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -158,15 +158,15 @@ class ChatView extends StatelessWidget { builder: (BuildContext context, snapshot) { var appbarBottomHeight = 0.0; if (controller.room.pinnedEventIds.isNotEmpty) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } if (scrollUpBannerEventId != null) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } final tombstoneEvent = controller.room.getState(EventTypes.RoomTombstone); if (tombstoneEvent != null) { - appbarBottomHeight += 42; + appbarBottomHeight += ChatAppBarListTile.fixedHeight; } return Scaffold( appBar: AppBar( diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 0940a786f..7afe413db 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -71,8 +71,8 @@ class PinnedEvents extends StatelessWidget { ) ?? L10n.of(context)!.loadingPleaseWait, leading: IconButton( - splashRadius: 20, - iconSize: 20, + splashRadius: 18, + iconSize: 18, color: Theme.of(context).colorScheme.onSurfaceVariant, icon: const Icon(Icons.push_pin), tooltip: L10n.of(context)!.unpin, From 3d965bf6ac7c06d01531e04041086114bd40a17e Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 18:31:27 +0200 Subject: [PATCH 07/13] chore: Follow up user bottom sheet --- lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 01035bb77..0b78c27f9 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -119,6 +119,8 @@ class UserBottomSheetView extends StatelessWidget { Padding( padding: const EdgeInsets.all(16.0), child: Avatar( + client: + Matrix.of(controller.widget.outerContext).client, mxContent: avatarUrl, name: displayname, size: Avatar.defaultSize * 2.5, From b1a9c6e92db32b69be43cf847ef60b71b97c9a2b Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 19:36:07 +0200 Subject: [PATCH 08/13] chore: Follow up user bottom sheet --- lib/widgets/avatar.dart | 1 + lib/widgets/mxc_image.dart | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index c3e24fe16..c8096ed85 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -79,6 +79,7 @@ class Avatar extends StatelessWidget { child: noPic ? textWidget : MxcImage( + client: client, key: ValueKey(mxContent.toString()), cacheKey: '${mxContent}_$size', uri: mxContent, diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index 34c53cb57..2126a8cbe 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -23,6 +23,7 @@ class MxcImage extends StatefulWidget { final ThumbnailMethod thumbnailMethod; final Widget Function(BuildContext context)? placeholder; final String? cacheKey; + final Client? client; const MxcImage({ this.uri, @@ -38,6 +39,7 @@ class MxcImage extends StatefulWidget { this.animationCurve = FluffyThemes.animationCurve, this.thumbnailMethod = ThumbnailMethod.scale, this.cacheKey, + this.client, super.key, }); @@ -64,7 +66,7 @@ class _MxcImageState extends State { bool? _isCached; Future _load() async { - final client = Matrix.of(context).client; + final client = widget.client ?? Matrix.of(context).client; final uri = widget.uri; final event = widget.event; From 3434741bc5f8b7d82e0e4c6cfb7f6d0c7472d05c Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:02:58 +0200 Subject: [PATCH 09/13] refactor: Clean up some widths --- lib/config/app_config.dart | 2 +- lib/pages/chat_list/navi_rail_item.dart | 4 ++-- lib/widgets/layouts/two_column_layout.dart | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 841d810ec..298b8743c 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -12,7 +12,7 @@ abstract class AppConfig { static double fontSizeFactor = 1; static const Color chatColor = primaryColor; static Color? colorSchemeSeed = primaryColor; - static const double messageFontSize = 15.75; + static const double messageFontSize = 16.0; static const bool allowOtherHomeservers = true; static const bool enableRegistration = true; static const Color primaryColor = Color(0xFF5625BA); diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index 66ad7c041..6cbb70e2e 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -33,8 +33,8 @@ class NaviRailItem extends StatelessWidget { return HoverBuilder( builder: (context, hovered) { return SizedBox( - height: 64, - width: 64, + height: FluffyThemes.navRailWidth, + width: FluffyThemes.navRailWidth, child: Stack( children: [ Positioned( diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index a6f4c8bdf..33003472e 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/themes.dart'; import 'package:flutter/material.dart'; class TwoColumnLayout extends StatelessWidget { @@ -20,7 +21,8 @@ class TwoColumnLayout extends StatelessWidget { Container( clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(), - width: 360.0 + (displayNavigationRail ? 64 : 0), + width: FluffyThemes.columnWidth + + (displayNavigationRail ? FluffyThemes.navRailWidth : 0), child: mainView, ), Container( From 30e3a4f2ff8bd840f23fc115efd1176cea66dc59 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:06:53 +0200 Subject: [PATCH 10/13] chore: Nicer background for sate message and time in chat --- lib/pages/chat/events/message.dart | 30 ++++++++++------------ lib/pages/chat/events/state_message.dart | 10 +++++--- lib/widgets/layouts/two_column_layout.dart | 3 ++- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 6a4ec3f2e..5f1e7dc35 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -435,22 +435,20 @@ class Message extends StatelessWidget { ? const EdgeInsets.symmetric(vertical: 8.0) : EdgeInsets.zero, child: Center( - child: Material( - color: displayTime - ? Theme.of(context).colorScheme.surface - : Theme.of(context).colorScheme.surface.withOpacity(0.33), - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - clipBehavior: Clip.antiAlias, - child: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - event.originServerTs.localizedTime(context), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12 * AppConfig.fontSizeFactor, - color: Theme.of(context).colorScheme.secondary, - ), + child: Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + event.originServerTs.localizedTime(context), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12 * AppConfig.fontSizeFactor, + color: Theme.of(context).colorScheme.secondary, + shadows: [ + Shadow( + color: Theme.of(context).colorScheme.surface, + blurRadius: 3, + ), + ], ), ), ), diff --git a/lib/pages/chat/events/state_message.dart b/lib/pages/chat/events/state_message.dart index 0aa5d9dc2..ebd6f7373 100644 --- a/lib/pages/chat/events/state_message.dart +++ b/lib/pages/chat/events/state_message.dart @@ -17,10 +17,6 @@ class StateMessage extends StatelessWidget { child: Center( child: Container( padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - ), child: Text( event.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), @@ -29,6 +25,12 @@ class StateMessage extends StatelessWidget { style: TextStyle( fontSize: 12 * AppConfig.fontSizeFactor, decoration: event.redacted ? TextDecoration.lineThrough : null, + shadows: [ + Shadow( + color: Theme.of(context).colorScheme.surface, + blurRadius: 3, + ), + ], ), ), ), diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 33003472e..cf1083a3f 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,6 +1,7 @@ -import 'package:fluffychat/config/themes.dart'; import 'package:flutter/material.dart'; +import 'package:fluffychat/config/themes.dart'; + class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; From 5b648624dae097755bd69ea0563a8e52e4b3c188 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 21:25:45 +0200 Subject: [PATCH 11/13] chore: Chat permissions page follow up --- assets/l10n/intl_en.arb | 3 ++- .../chat_permissions_settings_view.dart | 16 ++++++++++++++ .../permission_list_tile.dart | 22 ++++++++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index f7016107b..7132187c1 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2739,5 +2739,6 @@ "changeTheVisibilityOfChatHistory": "Change the visibility of the chat history", "changeTheCanonicalRoomAlias": "Change the main public chat address", "sendRoomNotifications": "Send a @room notifications", - "changeTheDescriptionOfTheGroup": "Change the description of the chat" + "changeTheDescriptionOfTheGroup": "Change the description of the chat", + "chatPermissionsDescription": "Define which power level is necessary for certain actions in this chat. The power levels 0, 50 and 100 are usually representing users, moderators and admins, but any gradation is possible." } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 50d46c08e..610f6b0e8 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -41,6 +41,22 @@ class ChatPermissionsSettingsView extends StatelessWidget { )..removeWhere((k, v) => v is! int); return Column( children: [ + ListTile( + leading: const Icon(Icons.info_outlined), + subtitle: Text( + L10n.of(context)!.chatPermissionsDescription, + ), + ), + Divider(color: Theme.of(context).dividerColor), + ListTile( + title: Text( + L10n.of(context)!.chatPermissions, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index 99f9e553b..118982d37 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -82,11 +82,8 @@ class PermissionsListTile extends StatelessWidget { style: Theme.of(context).textTheme.titleSmall, ), trailing: Material( - color: color.withAlpha(64), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - side: BorderSide(color: color), - ), + color: color.withAlpha(32), + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), child: DropdownButton( padding: const EdgeInsets.symmetric(horizontal: 8.0), borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), @@ -96,15 +93,24 @@ class PermissionsListTile extends StatelessWidget { items: [ DropdownMenuItem( value: permission < 50 ? permission : 0, - child: Text(L10n.of(context)!.userLevel(permission)), + child: Text( + L10n.of(context)!.userLevel(permission < 50 ? permission : 0), + ), ), DropdownMenuItem( value: permission < 100 && permission >= 50 ? permission : 50, - child: Text(L10n.of(context)!.moderatorLevel(permission)), + child: Text( + L10n.of(context)!.moderatorLevel( + permission < 100 && permission >= 50 ? permission : 50, + ), + ), ), DropdownMenuItem( value: permission >= 100 ? permission : 100, - child: Text(L10n.of(context)!.adminLevel(permission)), + child: Text( + L10n.of(context)! + .adminLevel(permission >= 100 ? permission : 100), + ), ), DropdownMenuItem( value: null, From c7b9acfcdf828a029cdbda5c3876cded45324c84 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 22:05:51 +0200 Subject: [PATCH 12/13] chore: Polish public room bottom sheet --- lib/widgets/public_room_bottom_sheet.dart | 111 +++++++++++++--------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 4f5806c2d..3ac568d8c 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import '../utils/localized_exception_extension.dart'; class PublicRoomBottomSheet extends StatelessWidget { final String? roomAlias; @@ -82,6 +81,7 @@ class PublicRoomBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { final roomAlias = this.roomAlias ?? chunk?.canonicalAlias; + final roomLink = roomAlias ?? chunk?.roomId; return SafeArea( child: Scaffold( appBar: AppBar( @@ -114,28 +114,71 @@ class PublicRoomBottomSheet extends StatelessWidget { return ListView( padding: EdgeInsets.zero, children: [ - if (profile == null) - Container( - height: 156, - alignment: Alignment.center, - color: Theme.of(context).secondaryHeaderColor, - child: snapshot.hasError - ? Text(snapshot.error!.toLocalizedString(context)) - : const CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ) - else - Center( - child: Padding( + Row( + children: [ + Padding( padding: const EdgeInsets.all(16.0), - child: Avatar( - mxContent: profile.avatarUrl, - name: profile.name ?? roomAlias, - size: Avatar.defaultSize * 3, + child: profile == null + ? const Center( + child: CircularProgressIndicator.adaptive(), + ) + : Avatar( + client: Matrix.of(outerContext).client, + mxContent: profile.avatarUrl, + name: profile.name ?? roomAlias, + size: Avatar.defaultSize * 3, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton.icon( + onPressed: roomLink != null + ? () => FluffyShare.share( + roomLink, + context, + copyOnly: true, + ) + : null, + icon: const Icon( + Icons.copy_outlined, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurface, + ), + label: Text( + roomLink ?? '...', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + TextButton.icon( + onPressed: () {}, + icon: const Icon( + Icons.groups_3_outlined, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.onSurface, + ), + label: Text( + L10n.of(context)!.countParticipants( + profile?.numJoinedMembers ?? 0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ), - ), + ], + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: ElevatedButton.icon( @@ -151,36 +194,10 @@ class PublicRoomBottomSheet extends StatelessWidget { ? L10n.of(context)!.joinSpace : L10n.of(context)!.joinRoom, ), - icon: const Icon(Icons.login_outlined), + icon: const Icon(Icons.navigate_next), ), ), const SizedBox(height: 16), - ListTile( - title: Text( - profile?.name ?? - roomAlias?.localpart ?? - chunk?.roomId.localpart ?? - L10n.of(context)!.chat, - ), - subtitle: Text( - '${L10n.of(context)!.participant}: ${profile?.numJoinedMembers ?? 0}', - ), - trailing: const Icon(Icons.account_box_outlined), - ), - if (roomAlias != null) - ListTile( - title: Text(L10n.of(context)!.publicLink), - subtitle: SelectableText(roomAlias), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16.0), - trailing: IconButton( - icon: const Icon(Icons.copy_outlined), - onPressed: () => FluffyShare.share( - roomAlias, - context, - ), - ), - ), if (profile?.topic?.isNotEmpty ?? false) ListTile( subtitle: SelectableLinkify( From 76b7fbf36f7446037b131b148643e5fc6c21203f Mon Sep 17 00:00:00 2001 From: krille-chan Date: Fri, 26 Jul 2024 22:12:03 +0200 Subject: [PATCH 13/13] chore: Follow up join space children --- lib/pages/chat_list/space_view.dart | 47 ++++++++--------------- lib/widgets/public_room_bottom_sheet.dart | 13 ++++--- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 5ad99faa8..94341ed9e 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -11,10 +11,12 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; +import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; enum AddRoomType { chat, subspace } @@ -95,38 +97,23 @@ class _SpaceViewState extends State { final client = Matrix.of(context).client; final space = client.getRoomById(widget.spaceId); - final consent = await showOkCancelAlertDialog( + final joined = await showAdaptiveBottomSheet( context: context, - title: item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat, - message: item.topic, - okLabel: L10n.of(context)!.joinRoom, - cancelLabel: L10n.of(context)!.cancel, + builder: (_) => PublicRoomBottomSheet( + outerContext: context, + chunk: item, + via: space?.spaceChildren + .firstWhereOrNull( + (child) => child.roomId == item.roomId, + ) + ?.via, + ), ); - if (consent != OkCancelResult.ok) return; - if (!mounted) return; - - await showFutureLoadingDialog( - context: context, - future: () async { - await client.joinRoom( - item.roomId, - serverName: space?.spaceChildren - .firstWhereOrNull( - (child) => child.roomId == item.roomId, - ) - ?.via, - ); - if (client.getRoomById(item.roomId) == null) { - // Wait for room actually appears in sync - await client.waitForRoomInSync(item.roomId, join: true); - } - }, - ); - if (!mounted) return; - - setState(() { - _discoveredChildren.remove(item); - }); + if (mounted && joined == true) { + setState(() { + _discoveredChildren.remove(item); + }); + } } void _onSpaceAction(SpaceActions action) async { diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index 3ac568d8c..3105cc576 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -15,13 +15,13 @@ class PublicRoomBottomSheet extends StatelessWidget { final String? roomAlias; final BuildContext outerContext; final PublicRoomsChunk? chunk; - final VoidCallback? onRoomJoined; + final List? via; PublicRoomBottomSheet({ this.roomAlias, required this.outerContext, this.chunk, - this.onRoomJoined, + this.via, super.key, }) { assert(roomAlias != null || chunk != null); @@ -38,8 +38,11 @@ class PublicRoomBottomSheet extends StatelessWidget { return chunk.roomId; } final roomId = chunk != null && knock - ? await client.knockRoom(chunk.roomId) - : await client.joinRoom(roomAlias ?? chunk!.roomId); + ? await client.knockRoom(chunk.roomId, serverName: via) + : await client.joinRoom( + roomAlias ?? chunk!.roomId, + serverName: via, + ); if (!knock && client.getRoomById(roomId) == null) { await client.waitForRoomInSync(roomId); @@ -51,7 +54,7 @@ class PublicRoomBottomSheet extends StatelessWidget { return; } if (result.error == null) { - Navigator.of(context).pop(); + Navigator.of(context).pop(true); // don't open the room if the joined room is a space if (chunk?.roomType != 'm.space' && !client.getRoomById(result.result!)!.isSpace) {