diff --git a/lib/pages/chat/chat_app_bar_list_tile.dart b/lib/pages/chat/chat_app_bar_list_tile.dart new file mode 100644 index 000000000..1e0ec8259 --- /dev/null +++ b/lib/pages/chat/chat_app_bar_list_tile.dart @@ -0,0 +1,61 @@ +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 { + final Widget? leading; + final String title; + final Widget? trailing; + final void Function()? onTap; + + const ChatAppBarListTile({ + super.key, + this.leading, + required this.title, + this.trailing, + this.onTap, + }); + + @override + 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, + overflow: TextOverflow.ellipsis, + fontSize: fontSize, + ), + 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, + ], + ), + ); + } +} diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 12c8e5b8e..7dd303655 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -8,13 +8,13 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/encryption_button.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; -import 'package:fluffychat/pages/chat/tombstone_display.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; @@ -155,6 +155,18 @@ class ChatView extends StatelessWidget { builder: (context, snapshot) => FutureBuilder( future: controller.loadTimelineFuture, builder: (BuildContext context, snapshot) { + var appbarBottomHeight = 0.0; + if (controller.room.pinnedEventIds.isNotEmpty) { + appbarBottomHeight += 42; + } + if (scrollUpBannerEventId != null) { + appbarBottomHeight += 42; + } + final tombstoneEvent = + controller.room.getState(EventTypes.RoomTombstone); + if (tombstoneEvent != null) { + appbarBottomHeight += 42; + } return Scaffold( appBar: AppBar( actionsIconTheme: IconThemeData( @@ -177,6 +189,50 @@ class ChatView extends StatelessWidget { titleSpacing: 0, title: ChatAppBarTitle(controller), actions: _appBarActions(context), + bottom: PreferredSize( + preferredSize: Size.fromHeight(appbarBottomHeight), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + PinnedEvents(controller), + if (tombstoneEvent != null) + ChatAppBarListTile( + title: tombstoneEvent.parsedTombstoneContent.body, + leading: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.upgrade_outlined), + ), + trailing: TextButton( + onPressed: controller.goToNewRoomAction, + child: Text(L10n.of(context)!.goToTheNewRoom), + ), + ), + if (scrollUpBannerEventId != null) + ChatAppBarListTile( + leading: IconButton( + color: + Theme.of(context).colorScheme.onSurfaceVariant, + icon: const Icon(Icons.close), + tooltip: L10n.of(context)!.close, + onPressed: () { + controller.discardScrollUpBannerEventId(); + controller.setReadMarker(); + }, + ), + title: L10n.of(context)!.jumpToLastReadMessage, + trailing: TextButton( + onPressed: () { + controller.scrollToEventId( + scrollUpBannerEventId, + ); + controller.discardScrollUpBannerEventId(); + }, + child: Text(L10n.of(context)!.jump), + ), + ), + ], + ), + ), ), floatingActionButton: controller.showScrollDownButton && controller.selectedEvents.isEmpty @@ -211,45 +267,6 @@ class ChatView extends StatelessWidget { SafeArea( child: Column( children: [ - TombstoneDisplay(controller), - if (scrollUpBannerEventId != null) - Material( - color: - Theme.of(context).colorScheme.surfaceVariant, - shape: Border( - bottom: BorderSide( - width: 1, - color: Theme.of(context).dividerColor, - ), - ), - child: ListTile( - leading: IconButton( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - icon: const Icon(Icons.close), - tooltip: L10n.of(context)!.close, - onPressed: () { - controller.discardScrollUpBannerEventId(); - controller.setReadMarker(); - }, - ), - title: Text( - L10n.of(context)!.jumpToLastReadMessage, - ), - contentPadding: const EdgeInsets.only(left: 8), - trailing: TextButton( - onPressed: () { - controller.scrollToEventId( - scrollUpBannerEventId, - ); - controller.discardScrollUpBannerEventId(); - }, - child: Text(L10n.of(context)!.jump), - ), - ), - ), - PinnedEvents(controller), Expanded( child: GestureDetector( onTap: controller.clearSingleSelectedEvent, diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 669ca737a..be4ea73ed 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -4,14 +4,12 @@ import 'package:flutter/material.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/utils/url_launcher.dart'; class PinnedEvents extends StatelessWidget { final ChatController controller; @@ -65,80 +63,32 @@ class PinnedEvents extends StatelessWidget { future: controller.room.getEventById(pinnedEventIds.last), builder: (context, snapshot) { final event = snapshot.data; - - if (event == null) { - return const SizedBox.shrink(); - } - - final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; - return Material( - color: Theme.of(context).colorScheme.surfaceVariant, - shape: Border( - bottom: BorderSide( - width: 1, - color: Theme.of(context).dividerColor, - ), + return FutureBuilder( + future: event?.calcLocalizedBody( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: true, + hideReply: true, ), - child: InkWell( - onTap: () => _displayPinnedEventsDialog(context), - child: Row( - children: [ - IconButton( - splashRadius: 20, - iconSize: 20, - color: Theme.of(context).colorScheme.onSurfaceVariant, - icon: const Icon(Icons.push_pin), - tooltip: L10n.of(context)!.unpin, - onPressed: - controller.room.canSendEvent(EventTypes.RoomPinnedEvents) - ? () => controller.unpinEvent(event.eventId) - : null, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: FutureBuilder( - future: event.calcLocalizedBody( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: true, - hideReply: true, - ), - builder: (context, snapshot) { - return Linkify( - text: snapshot.data ?? - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: true, - hideReply: true, - ), - options: const LinkifyOptions(humanize: false), - maxLines: 2, - style: TextStyle( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - overflow: TextOverflow.ellipsis, - fontSize: fontSize, - decoration: event.redacted - ? TextDecoration.lineThrough - : null, - ), - 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(), - ); - }, - ), - ), - ), - ], + builder: (context, snapshot) => ChatAppBarListTile( + title: snapshot.data ?? + event?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: true, + hideReply: true, + ) ?? + L10n.of(context)!.loadingPleaseWait, + leading: IconButton( + splashRadius: 20, + iconSize: 20, + color: Theme.of(context).colorScheme.onSurfaceVariant, + icon: const Icon(Icons.push_pin), + tooltip: L10n.of(context)!.unpin, + onPressed: + controller.room.canSendEvent(EventTypes.RoomPinnedEvents) + ? () => controller.unpinEvent(event!.eventId) + : null, ), + onTap: () => _displayPinnedEventsDialog(context), ), ); }, diff --git a/lib/pages/chat/tombstone_display.dart b/lib/pages/chat/tombstone_display.dart deleted file mode 100644 index e080a0009..000000000 --- a/lib/pages/chat/tombstone_display.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -import 'chat.dart'; - -class TombstoneDisplay extends StatelessWidget { - final ChatController controller; - const TombstoneDisplay(this.controller, {super.key}); - - @override - Widget build(BuildContext context) { - if (controller.room.getState(EventTypes.RoomTombstone) == null) { - return const SizedBox.shrink(); - } - return SizedBox( - height: 72, - child: Material( - color: Theme.of(context).colorScheme.surfaceVariant, - elevation: 1, - child: ListTile( - leading: CircleAvatar( - foregroundColor: Theme.of(context).colorScheme.onSecondary, - backgroundColor: Theme.of(context).colorScheme.secondary, - child: const Icon(Icons.upgrade_outlined), - ), - title: Text( - controller.room - .getState(EventTypes.RoomTombstone)! - .parsedTombstoneContent - .body, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - subtitle: Text(L10n.of(context)!.goToTheNewRoom), - onTap: controller.goToNewRoomAction, - ), - ), - ); - } -}