diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 491c1973..cddc3365 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -1327,5 +1327,7 @@ "pinnedPosts": "Pinned Posts", "thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders", "more": "More", - "collapse": "Collapse" + "collapse": "Collapse", + "pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.", + "discard": "Discard" } diff --git a/lib/route.dart b/lib/route.dart index f05455b5..b3f1fdee 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -44,7 +44,6 @@ import 'package:island/screens/discovery/feeds/feed_marketplace.dart'; import 'package:island/screens/discovery/feeds/feed_detail.dart'; import 'package:island/screens/creators/poll/poll_list.dart'; import 'package:island/screens/creators/webfeed/webfeed_list.dart'; -import 'package:island/screens/poll/poll_editor.dart'; import 'package:island/screens/posts/compose.dart'; import 'package:island/screens/posts/compose_article.dart'; import 'package:island/screens/posts/post_detail.dart'; @@ -486,28 +485,7 @@ final routerProvider = Provider((ref) { return CreatorPollListScreen(pubName: name); }, ), - // Poll routes - GoRoute( - name: 'creatorPollNew', - path: ':name/polls/new', - builder: (context, state) { - final name = state.pathParameters['name']!; - // initialPollId left null for create; initialPublisher prefilled - return PollEditorScreen(initialPublisher: name); - }, - ), - GoRoute( - name: 'creatorPollEdit', - path: ':name/polls/:id/edit', - builder: (context, state) { - final name = state.pathParameters['name']!; - final id = state.pathParameters['id']!; - return PollEditorScreen( - initialPollId: id, - initialPublisher: name, - ); - }, - ), + GoRoute( name: 'creatorStickers', path: ':name/stickers', diff --git a/lib/screens/creators/poll/poll_list.dart b/lib/screens/creators/poll/poll_list.dart index 491584bf..e30576e6 100644 --- a/lib/screens/creators/poll/poll_list.dart +++ b/lib/screens/creators/poll/poll_list.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/poll.dart'; import 'package:island/pods/network.dart'; +import 'package:island/screens/poll/poll_editor.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/poll/poll_feedback.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -73,10 +73,14 @@ class CreatorPollListScreen extends HookConsumerWidget { final String pubName; Future _createPoll(BuildContext context) async { - final result = await GoRouter.of( - context, - ).pushNamed('creatorPollNew', pathParameters: {'name': pubName}); - if (result is SnPollWithStats && context.mounted) { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + builder: (context) => PollEditorScreen(initialPublisher: pubName), + ); + if (result != null && context.mounted) { Navigator.of(context).maybePop(result); } } @@ -176,11 +180,20 @@ class _CreatorPollItem extends HookConsumerWidget { Text('edit').tr(), ], ), - onTap: () { - GoRouter.of(context).pushNamed( - 'creatorPollEdit', - pathParameters: {'name': pubName, 'id': pollWithStats.id}, + onTap: () async { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + builder: + (context) => PollEditorScreen( + initialPublisher: pubName, + initialPollId: pollWithStats.id, + ), ); + if (result != null && context.mounted) { + ref.invalidate(pollListNotifierProvider(pubName)); + } }, ), PopupMenuItem( diff --git a/lib/screens/poll/poll_editor.dart b/lib/screens/poll/poll_editor.dart index b6b2e4e1..5041ae5b 100644 --- a/lib/screens/poll/poll_editor.dart +++ b/lib/screens/poll/poll_editor.dart @@ -8,7 +8,7 @@ import 'package:island/pods/network.dart'; import 'package:island/talker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/models/poll.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/content/sheet.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:uuid/uuid.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -393,7 +393,7 @@ class PollEditorScreen extends ConsumerWidget { showSnackBar(isUpdate ? 'pollUpdated'.tr() : 'pollCreated'.tr()); if (!context.mounted) return; - Navigator.of(context).maybePop(res.data); + Navigator.of(context).maybePop(SnPoll.fromJson(res.data)); } catch (e) { showErrorAlert(e); } @@ -415,23 +415,46 @@ class PollEditorScreen extends ConsumerWidget { }); } - return AppScaffold( - isNoBackground: false, - appBar: AppBar( - title: Text(model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr()), - actions: [ - if (kDebugMode) - IconButton( - tooltip: 'pollPreviewJsonDebug'.tr(), - onPressed: () { - _showDebugPreview(context, model); - }, - icon: const Icon(Icons.visibility_outlined), - ), - const Gap(8), - ], - ), - body: Column( + return SheetScaffold( + titleText: model.id == null ? 'pollCreate'.tr() : 'pollEdit'.tr(), + actions: [ + if (kDebugMode) + IconButton( + tooltip: 'pollPreviewJsonDebug'.tr(), + onPressed: () { + _showDebugPreview(context, model); + }, + icon: const Icon(Icons.visibility_outlined), + ), + ], + heightFactor: 0.9, + onClose: () async { + final confirmed = await showDialog( + context: context, + builder: + (ctx) => AlertDialog( + title: Text('confirm'.tr()), + content: Text('pollConfirmDiscard'.tr()), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text('cancel'.tr()), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + style: TextButton.styleFrom( + foregroundColor: Theme.of(ctx).colorScheme.error, + ), + child: Text('discard'.tr()), + ), + ], + ), + ); + if (confirmed == true) { + Navigator.of(context).pop(); + } + }, + child: Column( children: [ Expanded( child: ConstrainedBox( diff --git a/lib/widgets/post/compose_poll.dart b/lib/widgets/post/compose_poll.dart index 65293efa..6950cff8 100644 --- a/lib/widgets/post/compose_poll.dart +++ b/lib/widgets/post/compose_poll.dart @@ -2,11 +2,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/poll.dart'; import 'package:island/models/publisher.dart'; import 'package:island/screens/creators/poll/poll_list.dart'; +import 'package:island/screens/poll/poll_editor.dart'; +import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; @@ -15,14 +16,13 @@ import 'package:island/widgets/post/publishers_modal.dart'; /// Bottom sheet for selecting or creating a poll. Returns SnPoll via Navigator.pop. class ComposePollSheet extends HookConsumerWidget { - /// Optional publisher name to filter polls and prefill creation. - final String? pubName; + final SnPublisher? pub; - const ComposePollSheet({super.key, this.pubName}); + const ComposePollSheet({super.key, this.pub}); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedPublisher = useState(pubName); + final selectedPublisher = useState(pub); final isPushing = useState(false); final errorText = useState(null); @@ -46,10 +46,11 @@ class ComposePollSheet extends HookConsumerWidget { children: [ // Link/Select existing poll list PagingHelperView( - provider: pollListNotifierProvider(pubName), - futureRefreshable: pollListNotifierProvider(pubName).future, + provider: pollListNotifierProvider(pub?.name), + futureRefreshable: + pollListNotifierProvider(pub?.name).future, notifierRefreshable: - pollListNotifierProvider(pubName).notifier, + pollListNotifierProvider(pub?.name).notifier, contentBuilder: (data, widgetCount, endItemView) => ListView.builder( padding: EdgeInsets.zero, @@ -81,38 +82,48 @@ class ComposePollSheet extends HookConsumerWidget { Text( 'pollCreateNewHint', ).tr().fontSize(13).opacity(0.85).padding(bottom: 8), - ListTile( - title: Text( - selectedPublisher.value == null - ? 'publisher'.tr() - : '@${selectedPublisher.value}', - ), - subtitle: Text( - selectedPublisher.value == null - ? 'publisherHint'.tr() - : 'selected'.tr(), - ), - leading: const Icon(Symbols.account_circle), - trailing: const Icon(Symbols.chevron_right), - onTap: () async { - final picked = - await showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => const PublisherModal(), - ); - if (picked != null) { - try { - final name = picked.name; - if (name.isNotEmpty) { - selectedPublisher.value = name; + Card( + child: ListTile( + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all( + Radius.circular(8), + ), + ), + title: Text( + selectedPublisher.value == null + ? 'publisher'.tr() + : selectedPublisher.value!.nick, + ), + subtitle: Text( + selectedPublisher.value == null + ? 'publisherHint'.tr() + : '@${selectedPublisher.value?.name}', + ), + leading: + selectedPublisher.value == null + ? const Icon(Symbols.account_circle) + : ProfilePictureWidget( + file: selectedPublisher.value?.picture, + ), + trailing: const Icon(Symbols.chevron_right), + onTap: () async { + final picked = + await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => const PublisherModal(), + ); + if (picked != null) { + try { + selectedPublisher.value = picked; errorText.value = null; + } catch (_) { + // ignore } - } catch (_) { - // ignore } - } - }, + }, + ), ), if (errorText.value != null) Padding( @@ -146,8 +157,7 @@ class ComposePollSheet extends HookConsumerWidget { isPushing.value ? null : () async { - final pub = selectedPublisher.value ?? ''; - if (pub.isEmpty) { + if (pub == null) { errorText.value = 'publisherCannotBeEmpty'.tr(); return; @@ -155,12 +165,18 @@ class ComposePollSheet extends HookConsumerWidget { errorText.value = null; isPushing.value = true; - // Push to creatorPollNew route and await result - final result = await GoRouter.of( - context, - ).push( - '/creators/$pub/polls/new', - ); + // Show modal bottom sheet with poll editor and await result + final result = + await showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + builder: + (context) => PollEditorScreen( + initialPublisher: pub?.name, + ), + ); if (result == null) { isPushing.value = false; diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index eed6f224..35d7db5f 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -611,7 +611,7 @@ class ComposeLogic { context: context, useRootNavigator: true, isScrollControlled: true, - builder: (context) => const ComposePollSheet(), + builder: (context) => ComposePollSheet(pub: state.currentPublisher.value), ); if (poll == null) return;