diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index c90ca6d55..7c251d585 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4180,5 +4180,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." } \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index c6dae0aa7..d8125169f 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -19,7 +19,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; // #Pangea 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 4447599f1..1327141e2 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -178,15 +178,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/events/message.dart b/lib/pages/chat/events/message.dart index 0ac258f18..cf3b8689a 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -557,22 +557,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/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, diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 0afa56086..393c2a743 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -32,10 +32,7 @@ class ChatDetailsView extends StatelessWidget { @override Widget build(BuildContext context) { final room = Matrix.of(context).client.getRoomById(controller.roomId!); - // #Pangea - if (room == null || room.membership == Membership.leave) { - // if (room == null) { - // Pangea# + if (room == null) { return Scaffold( appBar: AppBar( title: Text(L10n.of(context)!.oopsSomethingWentWrong), @@ -62,31 +59,20 @@ class ChatDetailsView extends StatelessWidget { ); return Scaffold( appBar: AppBar( - leading: - // #Pangea - !room.isSpace - ? - // Pangea# - controller.widget.embeddedCloseButton ?? - const Center(child: BackButton()) - // #Pangea - : BackButton( - onPressed: () => context.go("/rooms"), - ) - // Pangea# - , + leading: controller.widget.embeddedCloseButton ?? + const Center(child: BackButton()), elevation: Theme.of(context).appBarTheme.elevation, actions: [ // #Pangea // if (room.canonicalAlias.isNotEmpty) - // IconButton( - // tooltip: L10n.of(context)!.share, - // icon: Icon(Icons.adaptive.share_outlined), - // onPressed: () => FluffyShare.share( - // AppConfig.inviteLinkPrefix + room.canonicalAlias, - // context, + // IconButton( + // tooltip: L10n.of(context)!.share, + // icon: Icon(Icons.adaptive.share_outlined), + // onPressed: () => FluffyShare.share( + // AppConfig.inviteLinkPrefix + room.canonicalAlias, + // context, + // ), // ), - // ), // Pangea# if (controller.widget.embeddedCloseButton == null) ChatSettingsPopupMenu(room, false), @@ -122,32 +108,12 @@ 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, - ), - ), - // #Pangea - // Hero animation is causing weird visual glitch - // Probably not worth keeping - // child: Hero( - // tag: controller.widget - // .embeddedCloseButton != - // null - // ? 'embedded_content_banner' - // : 'content_banner', - // Pangea# + Hero( + tag: controller + .widget.embeddedCloseButton != + null + ? 'embedded_content_banner' + : 'content_banner', child: Avatar( mxContent: room.avatar, name: displayname, @@ -211,7 +177,7 @@ class ChatDetailsView extends StatelessWidget { : displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 18), ), ), TextButton.icon( @@ -243,13 +209,9 @@ class ChatDetailsView extends StatelessWidget { ), ], ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), - // if (room.canSendEvent('m.room.name')) + + // #Pangea if (room.isRoomAdmin) - // #Pangea ClassNameButton( room: room, controller: controller, @@ -259,32 +221,18 @@ class ChatDetailsView extends StatelessWidget { room: room, controller: controller, ), - // #Pangea RoomCapacityButton( room: room, controller: controller, ), - // Pangea# - // commenting out language settings in spaces for now - // if (room.languageSettings != null && room.isRoomAdmin) - // LanguageSettings( - // roomId: controller.roomId, - // startOpen: false, - // ), - - // Commenting out pangea room rules for now - // if (room.pangeaRoomRules != null) - // RoomRulesEditor( - // roomId: controller.roomId, - // startOpen: false, - // ), - + // Divider(color: Theme.of(context).dividerColor), // if (!room.canChangeStateEvent(EventTypes.RoomTopic)) // ListTile( // title: Text( // L10n.of(context)!.chatDescription, // style: TextStyle( - // color: Theme.of(context).colorScheme.secondary, + // color: + // Theme.of(context).colorScheme.secondary, // fontWeight: FontWeight.bold, // ), // ), @@ -294,7 +242,8 @@ class ChatDetailsView extends StatelessWidget { // padding: const EdgeInsets.all(16.0), // child: TextButton.icon( // onPressed: controller.setTopicAction, - // label: Text(L10n.of(context)!.setChatDescription), + // label: + // Text(L10n.of(context)!.setChatDescription), // icon: const Icon(Icons.edit_outlined), // style: TextButton.styleFrom( // backgroundColor: Theme.of(context) @@ -324,20 +273,21 @@ class ChatDetailsView extends StatelessWidget { // fontStyle: room.topic.isEmpty // ? FontStyle.italic // : FontStyle.normal, - // color: - // Theme.of(context).textTheme.bodyMedium!.color, - // decorationColor: - // Theme.of(context).textTheme.bodyMedium!.color, + // color: Theme.of(context) + // .textTheme + // .bodyMedium! + // .color, + // decorationColor: Theme.of(context) + // .textTheme + // .bodyMedium! + // .color, // ), // onOpen: (url) => // UrlLauncher(context, url.url).launchUrl(), // ), // ), // const SizedBox(height: 16), - // Divider( - // height: 1, - // color: Theme.of(context).dividerColor, - // ), + // Divider(color: Theme.of(context).dividerColor), // ListTile( // leading: CircleAvatar( // backgroundColor: @@ -354,23 +304,25 @@ class ChatDetailsView extends StatelessWidget { // trailing: const Icon(Icons.chevron_right_outlined), // ), // if (!room.isDirectChat) - // ListTile( - // leading: CircleAvatar( - // backgroundColor: - // Theme.of(context).scaffoldBackgroundColor, - // foregroundColor: iconColor, - // child: const Icon(Icons.shield_outlined), + // ListTile( + // leading: CircleAvatar( + // backgroundColor: + // Theme.of(context).scaffoldBackgroundColor, + // foregroundColor: iconColor, + // child: const Icon(Icons.shield_outlined), + // ), + // title: Text( + // L10n.of(context)!.accessAndVisibility, + // ), + // subtitle: Text( + // L10n.of(context)! + // .accessAndVisibilityDescription, + // ), + // onTap: () => context + // .push('/rooms/${room.id}/details/access'), + // trailing: + // const Icon(Icons.chevron_right_outlined), // ), - // title: Text( - // L10n.of(context)!.accessAndVisibility, - // ), - // subtitle: Text( - // L10n.of(context)!.accessAndVisibilityDescription, - // ), - // onTap: () => context - // .push('/rooms/${room.id}/details/access'), - // trailing: const Icon(Icons.chevron_right_outlined), - // ), // if (!room.isDirectChat) if (!room.isDirectChat && !room.isSpace && @@ -399,17 +351,13 @@ class ChatDetailsView extends StatelessWidget { Icons.edit_attributes_outlined, ), ), - // #Pangea - // trailing: const Icon(Icons.chevron_right_outlined), - // Pangea# + trailing: + const Icon(Icons.chevron_right_outlined), onTap: () => context.push( '/rooms/${room.id}/details/permissions', ), ), - Divider( - height: 1, - color: Theme.of(context).dividerColor, - ), + Divider(color: Theme.of(context).dividerColor), // #Pangea if (room.canInvite && !room.isDirectChat && @@ -486,8 +434,8 @@ class ChatDetailsView extends StatelessWidget { ), ), onTap: () async { - OkCancelResult confirmed = OkCancelResult.ok; - bool shouldGo = false; + var confirmed = OkCancelResult.ok; + var shouldGo = false; // archiveSpace has its own popup; only show if not space if (!room.isSpace) { confirmed = await showOkCancelAlertDialog( @@ -539,10 +487,10 @@ class ChatDetailsView extends StatelessWidget { ), ), onTap: () async { - OkCancelResult confirmed = OkCancelResult.ok; - bool shouldGo = false; + var confirmed = OkCancelResult.ok; + var shouldGo = false; // If user is only admin, room will be archived - final bool onlyAdmin = await room.isOnlyAdmin(); + final onlyAdmin = await room.isOnlyAdmin(); // archiveSpace has its own popup; only show if not space if (!room.isSpace) { confirmed = await showOkCancelAlertDialog( @@ -649,8 +597,10 @@ class ChatDetailsView extends StatelessWidget { // radius: Avatar.defaultSize / 2, // child: const Icon(Icons.add_outlined), // ), - // trailing: const Icon(Icons.chevron_right_outlined), - // onTap: () => context.go('/rooms/${room.id}/invite'), + // trailing: + // const Icon(Icons.chevron_right_outlined), + // onTap: () => + // context.go('/rooms/${room.id}/invite'), // ), // Pangea# ], diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index c8772f9e8..c972c0b14 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -831,9 +831,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/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/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 53777162d..363d92cb6 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -3,10 +3,12 @@ import 'package:collection/collection.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'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; @@ -93,38 +95,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/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 2b1dff187..11c834b50 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -57,6 +57,22 @@ class ChatPermissionsSettingsView extends StatelessWidget { // Pangea# 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 e319e7467..b65b403d8 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -80,11 +80,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), @@ -94,15 +91,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, diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index 0acf89605..e3486e6a9 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -129,10 +129,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 034d049f7..a16e1bdba 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -95,10 +95,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 c7e9d6e64..7adc17de3 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -8,7 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version +import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'settings.dart'; @@ -20,7 +20,7 @@ class SettingsView extends StatelessWidget { // #Pangea Future getAppVersion(BuildContext context) async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + final packageInfo = await PackageInfo.fromPlatform(); return L10n.of(context)! .versionText(packageInfo.version, packageInfo.buildNumber); } @@ -28,9 +28,7 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { - // #Pangea - // final showChatBackupBanner = controller.showChatBackupBanner; - // Pangea# + final showChatBackupBanner = controller.showChatBackupBanner; return Scaffold( appBar: AppBar( leading: Center( @@ -39,13 +37,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, @@ -66,32 +57,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), @@ -119,7 +95,9 @@ class SettingsView extends StatelessWidget { displayname, maxLines: 1, overflow: TextOverflow.ellipsis, - // style: const TextStyle(fontSize: 18), + style: const TextStyle( + fontSize: 18, + ), ), ), TextButton.icon( @@ -147,10 +125,7 @@ class SettingsView extends StatelessWidget { }, ), // #Pangea - // Divider( - // height: 1, - // color: Theme.of(context).dividerColor, - // ), + // Divider(color: Theme.of(context).dividerColor), // if (showChatBackupBanner == null) // ListTile( // leading: const Icon(Icons.backup_outlined), @@ -166,7 +141,6 @@ class SettingsView extends StatelessWidget { // onChanged: controller.firstRunBootstrapAction, // ), // Divider( - // height: 1, // color: Theme.of(context).dividerColor, // ), // Pangea# @@ -174,52 +148,40 @@ class SettingsView extends StatelessWidget { 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), ), // #Pangea ListTile( leading: const Icon(Icons.account_circle_outlined), title: Text(L10n.of(context)!.subscriptionManagement), onTap: () => context.go('/rooms/settings/subscription'), - trailing: const Icon( - Icons.chevron_right_outlined, - ), ), ListTile( leading: const Icon(Icons.psychology_outlined), title: Text(L10n.of(context)!.learningSettings), onTap: () => context.go('/rooms/settings/learning'), - trailing: const Icon(Icons.chevron_right_outlined), ), // Pangea# 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), @@ -229,7 +191,7 @@ class SettingsView extends StatelessWidget { await showFutureLoadingDialog( context: context, future: () async { - final String roomId = + final roomId = await Matrix.of(context).client.startDirectChat( Environment.supportUserId, enableEncryption: false, @@ -239,20 +201,17 @@ class SettingsView extends StatelessWidget { ); }, // Pangea# - 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), ), // #Pangea // 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), // ), ListTile( leading: const Icon(Icons.shield_outlined), @@ -290,6 +249,12 @@ class SettingsView extends StatelessWidget { title: Text(L10n.of(context)!.connectedToStaging), ), // Pangea# + 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 d54f194c5..fcae861e8 100644 --- a/lib/pages/settings_3pid/settings_3pid_view.dart +++ b/lib/pages/settings_3pid/settings_3pid_view.dart @@ -67,7 +67,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 25a4dd0a5..20e179cb6 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 { defaultValue: AppConfig.swipeRightToLeftToReply, ), // #Pangea - // 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 58746bd9e..5cb988070 100644 --- a/lib/pages/settings_security/settings_security_view.dart +++ b/lib/pages/settings_security/settings_security_view.dart @@ -85,7 +85,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 7446aebc7..7a19043e8 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -236,6 +236,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(widget.outerContext).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 ef79c0e31..25552e51b 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -2,6 +2,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/utils/bot_name.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( @@ -36,78 +38,20 @@ 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, - ), - ], - ); - }, - ), - ], - ), - actions: [ - if (userId != client.userID && - !client.ignoredUsers.contains(userId) - // #Pangea - && - userId != BotName.byEnvironment - // Pangea# - ) - 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), - ), - ), - ], + title: Text(displayname), + actions: dmRoomId == null + ? null + : [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + 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( @@ -174,25 +118,12 @@ 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( + client: + Matrix.of(controller.widget.outerContext).client, + mxContent: avatarUrl, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), Expanded( @@ -200,26 +131,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, @@ -232,37 +143,72 @@ 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, + ), + ], + ); + }, + ), ], ), ), ], ), - if (userId != client.userID) - Padding( - padding: const EdgeInsets.symmetric( - 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, - ), - ), - ), PresenceBuilder( userId: userId, client: client, @@ -286,6 +232,49 @@ class UserBottomSheetView extends StatelessWidget { ); }, ), + if (userId != client.userID) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + 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, + 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, + ), + ), + ), + ), if (controller.widget.onMention != null) ListTile( leading: const Icon(Icons.alternate_email_outlined), @@ -339,8 +328,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, @@ -376,7 +365,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), // ), @@ -392,6 +381,21 @@ class UserBottomSheetView extends StatelessWidget { style: const TextStyle(color: Colors.orange), ), ), + if (userId != client.userID && + !client.ignoredUsers.contains(userId) + // #Pangea + && + userId != BotName.byEnvironment + // Pangea# + ) + 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), + ), ], ); }, 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/avatar.dart b/lib/widgets/avatar.dart index 39af2e756..3f2bd5fac 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -77,6 +77,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/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, ); }, ), diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index a6f4c8bdf..cf1083a3f 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:fluffychat/config/themes.dart'; + class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; @@ -20,7 +22,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( 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; diff --git a/lib/widgets/public_room_bottom_sheet.dart b/lib/widgets/public_room_bottom_sheet.dart index b60b5a1ee..e07728315 100644 --- a/lib/widgets/public_room_bottom_sheet.dart +++ b/lib/widgets/public_room_bottom_sheet.dart @@ -10,19 +10,17 @@ import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import '../utils/localized_exception_extension.dart'; - 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); @@ -39,8 +37,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); @@ -58,7 +59,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) { @@ -70,17 +71,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); } @@ -88,6 +89,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,41 +116,84 @@ class PublicRoomBottomSheet extends StatelessWidget { ], ), body: FutureBuilder( - future: _search(context), + future: _search(), builder: (context, snapshot) { final profile = snapshot.data; 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( onPressed: () => _joinRoom(context), label: Text( chunk?.joinRule == 'knock' && - Matrix.of(context) + Matrix.of(outerContext) .client .getRoomById(chunk!.roomId) == null @@ -157,36 +202,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(