diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 9d991940..d83078b8 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -20,6 +20,7 @@ import 'package:island/widgets/check_in.dart'; import 'package:island/widgets/post/post_featured.dart'; import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/compose_card.dart'; +import 'package:island/widgets/post/compose_dialog.dart'; import 'package:island/screens/tabs.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -43,6 +44,7 @@ Widget notificationIndicatorWidget( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), + minTileHeight: 48, leading: const Icon(Symbols.notifications), title: Row( children: [ @@ -109,7 +111,7 @@ class ExploreScreen extends HookConsumerWidget { final isWide = isWideScreen(context); final filterBar = Card( - margin: EdgeInsets.zero, + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), child: Row( children: [ Expanded( @@ -122,28 +124,19 @@ class ExploreScreen extends HookConsumerWidget { Tab( icon: Tooltip( message: 'explore'.tr(), - child: Icon( - Symbols.explore, - color: Theme.of(context).appBarTheme.foregroundColor!, - ), + child: Icon(Symbols.explore), ), ), Tab( icon: Tooltip( message: 'exploreFilterSubscriptions'.tr(), - child: Icon( - Symbols.subscriptions, - color: Theme.of(context).appBarTheme.foregroundColor!, - ), + child: Icon(Symbols.subscriptions), ), ), Tab( icon: Tooltip( message: 'exploreFilterFriends'.tr(), - child: Icon( - Symbols.people, - color: Theme.of(context).appBarTheme.foregroundColor!, - ), + child: Icon(Symbols.people), ), ), ], @@ -153,10 +146,7 @@ class ExploreScreen extends HookConsumerWidget { onPressed: () { context.pushNamed('articles'); }, - icon: Icon( - Symbols.auto_stories, - color: Theme.of(context).appBarTheme.foregroundColor!, - ), + icon: Icon(Symbols.auto_stories), tooltip: 'webArticlesStand'.tr(), ), PopupMenuButton( @@ -211,10 +201,7 @@ class ExploreScreen extends HookConsumerWidget { }, ), ], - icon: Icon( - Symbols.action_key, - color: Theme.of(context).appBarTheme.foregroundColor!, - ), + icon: Icon(Symbols.action_key), tooltip: 'search'.tr(), ), ], @@ -227,23 +214,19 @@ class ExploreScreen extends HookConsumerWidget { isWide ? null : InkWell( - onLongPress: () { - context - .pushNamed('postCompose', queryParameters: {'type': '1'}) - .then((value) { - if (value != null) { - activitiesNotifier.forceRefresh(); - } - }); + onLongPress: () async { + final result = await PostComposeDialog.show(context); + if (result != null) { + activitiesNotifier.forceRefresh(); + } }, child: FloatingActionButton( heroTag: Key("explore-page-fab"), - onPressed: () { - context.pushNamed('postCompose').then((value) { - if (value != null) { - activitiesNotifier.forceRefresh(); - } - }); + onPressed: () async { + final result = await PostComposeDialog.show(context); + if (result != null) { + activitiesNotifier.forceRefresh(); + } }, child: const Icon(Symbols.edit), ), diff --git a/lib/widgets/post/compose_card.dart b/lib/widgets/post/compose_card.dart index 97bf8ede..a54f13cd 100644 --- a/lib/widgets/post/compose_card.dart +++ b/lib/widgets/post/compose_card.dart @@ -155,6 +155,7 @@ class PostComposeCard extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, builder: (context) => ComposeSettingsSheet(state: state), ); } @@ -278,6 +279,7 @@ class PostComposeCard extends HookConsumerWidget { final config = await showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, builder: (context) => AttachmentUploaderSheet( ref: ref, @@ -325,6 +327,7 @@ class PostComposeCard extends HookConsumerWidget { await showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, builder: (context) => AttachmentUploaderSheet( ref: ref, @@ -370,6 +373,7 @@ class PostComposeCard extends HookConsumerWidget { children: [ // Header with actions Container( + height: 65, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border( @@ -393,7 +397,7 @@ class PostComposeCard extends HookConsumerWidget { tooltip: 'postSettings'.tr(), visualDensity: const VisualDensity( horizontal: -4, - vertical: -4, + vertical: -2, ), ), IconButton( @@ -418,7 +422,7 @@ class PostComposeCard extends HookConsumerWidget { : 'postPublish'.tr(), visualDensity: const VisualDensity( horizontal: -4, - vertical: -4, + vertical: -2, ), ), if (onCancel != null) @@ -428,7 +432,7 @@ class PostComposeCard extends HookConsumerWidget { tooltip: 'cancel'.tr(), visualDensity: const VisualDensity( horizontal: -4, - vertical: -4, + vertical: -2, ), ), ], @@ -473,6 +477,7 @@ class PostComposeCard extends HookConsumerWidget { onTap: () { showModalBottomSheet( isScrollControlled: true, + useRootNavigator: true, context: context, builder: (context) => const PublisherModal(), ).then((value) { @@ -570,12 +575,19 @@ class PostComposeCard extends HookConsumerWidget { ), // Bottom toolbar - ClipRRect( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(8), - bottomRight: Radius.circular(8), + SizedBox( + height: 65, + child: ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + child: ComposeToolbar( + state: state, + originalPost: originalPost, + isCompact: true, + ), ), - child: ComposeToolbar(state: state, originalPost: originalPost), ), ], ), @@ -721,6 +733,7 @@ class PostComposeCard extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, + useRootNavigator: true, backgroundColor: Colors.transparent, builder: (context) => DraggableScrollableSheet( diff --git a/lib/widgets/post/compose_toolbar.dart b/lib/widgets/post/compose_toolbar.dart index 824a2d99..2d0ef86d 100644 --- a/lib/widgets/post/compose_toolbar.dart +++ b/lib/widgets/post/compose_toolbar.dart @@ -12,8 +12,14 @@ import 'package:styled_widget/styled_widget.dart'; class ComposeToolbar extends HookConsumerWidget { final ComposeState state; final SnPost? originalPost; + final bool isCompact; - const ComposeToolbar({super.key, required this.state, this.originalPost}); + const ComposeToolbar({ + super.key, + required this.state, + this.originalPost, + this.isCompact = false, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -74,6 +80,160 @@ class ComposeToolbar extends HookConsumerWidget { final colorScheme = Theme.of(context).colorScheme; + if (isCompact) { + return Container( + color: Theme.of(context).colorScheme.surfaceContainerLow, + padding: EdgeInsets.symmetric(horizontal: 8), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + IconButton( + onPressed: pickPhotoMedia, + tooltip: 'addPhoto'.tr(), + icon: const Icon(Symbols.add_a_photo), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + IconButton( + onPressed: pickVideoMedia, + tooltip: 'addVideo'.tr(), + icon: const Icon(Symbols.videocam), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + IconButton( + onPressed: addAudio, + tooltip: 'addAudio'.tr(), + icon: const Icon(Symbols.mic), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + IconButton( + onPressed: pickGeneralFile, + tooltip: 'uploadFile'.tr(), + icon: const Icon(Symbols.file_upload), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + IconButton( + onPressed: linkAttachment, + icon: const Icon(Symbols.attach_file), + tooltip: 'linkAttachment'.tr(), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + ), + // Poll button with visual state when a poll is linked + ListenableBuilder( + listenable: state.pollId, + builder: (context, _) { + return IconButton( + onPressed: pickPoll, + icon: const Icon(Symbols.how_to_vote), + tooltip: 'poll'.tr(), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + state.pollId.value != null + ? colorScheme.primary.withOpacity(0.15) + : null, + ), + ), + ); + }, + ), + // Embed button with visual state when embed is present + ListenableBuilder( + listenable: state.embedView, + builder: (context, _) { + return IconButton( + onPressed: showEmbedSheet, + icon: const Icon(Symbols.iframe), + tooltip: 'embedView'.tr(), + color: colorScheme.primary, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -2, + ), + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + state.embedView.value != null + ? colorScheme.primary.withOpacity(0.15) + : null, + ), + ), + ); + }, + ), + ], + ), + ), + ), + if (originalPost == null && state.isEmpty) + IconButton( + icon: const Icon(Symbols.draft, size: 20), + color: colorScheme.primary, + onPressed: showDraftManager, + tooltip: 'drafts'.tr(), + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -4, + ), + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + ) + else if (originalPost == null) + IconButton( + icon: const Icon(Symbols.save, size: 20), + color: colorScheme.primary, + onPressed: saveDraft, + onLongPress: showDraftManager, + tooltip: 'saveDraft'.tr(), + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -4, + ), + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + ), + ], + ).padding(horizontal: 8, vertical: 4), + ), + ), + ); + } + return Material( elevation: 4, color: Theme.of(context).colorScheme.surfaceContainerLow, diff --git a/lib/widgets/post/post_featured.dart b/lib/widgets/post/post_featured.dart index 4ff11709..cc1a348d 100644 --- a/lib/widgets/post/post_featured.dart +++ b/lib/widgets/post/post_featured.dart @@ -102,72 +102,75 @@ class PostFeaturedList extends HookConsumerWidget { margin: EdgeInsets.zero, child: Column( children: [ - Row( - spacing: 8, - children: [ - const Icon(Symbols.highlight), - const Text('highlightPost').tr(), - Spacer(), - IconButton( - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - constraints: const BoxConstraints(), - onPressed: () { - pageViewController.animateToPage( - pageViewCurrent.value - 1, - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - ); - }, - icon: const Icon(Symbols.arrow_left), - ), - IconButton( - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - constraints: const BoxConstraints(), - onPressed: () { - pageViewController.animateToPage( - pageViewCurrent.value + 1, - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - ); - }, - icon: const Icon(Symbols.arrow_right), - ), - IconButton( - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - constraints: const BoxConstraints(), - onPressed: () { - isCollapsed.value = !isCollapsed.value; - debugPrint( - 'PostFeaturedList: Manual toggle. isCollapsed set to ${isCollapsed.value}', - ); - if (isCollapsed.value && - featuredPostsAsync.hasValue && - featuredPostsAsync.value!.isNotEmpty) { - prefs.setString( - kFeaturedPostsCollapsedId, - featuredPostsAsync.value!.first.id, + SizedBox( + height: 48, + child: Row( + spacing: 8, + children: [ + const Icon(Symbols.highlight), + const Text('highlightPost').tr(), + Spacer(), + IconButton( + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { + pageViewController.animateToPage( + pageViewCurrent.value - 1, + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, ); - debugPrint( - 'PostFeaturedList: Stored collapsed ID: ${featuredPostsAsync.value!.first.id}', - ); - } else { - prefs.remove(kFeaturedPostsCollapsedId); - debugPrint( - 'PostFeaturedList: Removed stored collapsed ID.', - ); - } - }, - icon: Icon( - isCollapsed.value - ? Symbols.expand_more - : Symbols.expand_less, + }, + icon: const Icon(Symbols.arrow_left), ), - ), - ], - ).padding(horizontal: 16, vertical: 8), + IconButton( + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { + pageViewController.animateToPage( + pageViewCurrent.value + 1, + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + ); + }, + icon: const Icon(Symbols.arrow_right), + ), + IconButton( + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { + isCollapsed.value = !isCollapsed.value; + debugPrint( + 'PostFeaturedList: Manual toggle. isCollapsed set to ${isCollapsed.value}', + ); + if (isCollapsed.value && + featuredPostsAsync.hasValue && + featuredPostsAsync.value!.isNotEmpty) { + prefs.setString( + kFeaturedPostsCollapsedId, + featuredPostsAsync.value!.first.id, + ); + debugPrint( + 'PostFeaturedList: Stored collapsed ID: ${featuredPostsAsync.value!.first.id}', + ); + } else { + prefs.remove(kFeaturedPostsCollapsedId); + debugPrint( + 'PostFeaturedList: Removed stored collapsed ID.', + ); + } + }, + icon: Icon( + isCollapsed.value + ? Symbols.expand_more + : Symbols.expand_less, + ), + ), + ], + ).padding(horizontal: 16, vertical: 8), + ), AnimatedSize( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 73f11c68..385b4451 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -24,6 +24,7 @@ import 'package:island/widgets/post/post_shared.dart'; import 'package:island/widgets/post/embed_view_renderer.dart'; import 'package:island/widgets/safety/abuse_report_helper.dart'; import 'package:island/widgets/share/share_sheet.dart'; +import 'package:island/widgets/post/compose_dialog.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart' show getTemporaryDirectory; import 'package:screenshot/screenshot.dart'; @@ -174,14 +175,14 @@ class PostActionableItem extends HookConsumerWidget { MenuAction( title: 'edit'.tr(), image: MenuImage.icon(Symbols.edit), - callback: () { - context - .pushNamed('postEdit', pathParameters: {'id': item.id}) - .then((value) { - if (value != null) { - onRefresh?.call(); - } - }); + callback: () async { + final result = await PostComposeDialog.show( + context, + originalPost: item, + ); + if (result != null) { + onRefresh?.call(); + } }, ), if (isAuthor) @@ -221,21 +222,27 @@ class PostActionableItem extends HookConsumerWidget { MenuAction( title: 'reply'.tr(), image: MenuImage.icon(Symbols.reply), - callback: () { - context.pushNamed( - 'postCompose', - extra: PostComposeInitialState(replyingTo: item), + callback: () async { + final result = await PostComposeDialog.show( + context, + initialState: PostComposeInitialState(replyingTo: item), ); + if (result != null) { + onRefresh?.call(); + } }, ), MenuAction( title: 'forward'.tr(), image: MenuImage.icon(Symbols.forward), - callback: () { - context.pushNamed( - 'postCompose', - extra: PostComposeInitialState(forwardingTo: item), + callback: () async { + final result = await PostComposeDialog.show( + context, + initialState: PostComposeInitialState(forwardingTo: item), ); + if (result != null) { + onRefresh?.call(); + } }, ), if (isAuthor && item.pinMode == null)