From 1b790baee1cd6bc1505faf2f5802516451d37de8 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 30 Apr 2025 00:54:43 +0800 Subject: [PATCH] :sparkles: Publisher selection --- assets/i18n/en-US.json | 4 +- lib/screens/account/me/publishers.dart | 2 +- lib/screens/posts/compose.dart | 20 +++++- lib/widgets/alert.dart | 5 +- lib/widgets/post/post_item.dart | 4 +- lib/widgets/post/post_quick_reply.dart | 18 ++++- lib/widgets/post/publishers_modal.dart | 91 ++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 lib/widgets/post/publishers_modal.dart diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 104d81c..7803aa9 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -46,5 +46,7 @@ "postCreateAccountTitle": "Thanks for joining!", "postCreateAccountNext": "What's next?", "postCreateAccountNext1": "Go to your email inbox and receive the account activation email.", - "postCreateAccountNext2": "Log in to your account and start exploring the Solar Network!" + "postCreateAccountNext2": "Log in to your account and start exploring the Solar Network!", + "publishersEmpty": "No publishers yet", + "publishersEmptyDescription": "You can need to create a publisher to start publishing your posts." } diff --git a/lib/screens/account/me/publishers.dart b/lib/screens/account/me/publishers.dart index 94321de..f1b4979 100644 --- a/lib/screens/account/me/publishers.dart +++ b/lib/screens/account/me/publishers.dart @@ -246,7 +246,7 @@ class EditPublisherScreen extends HookConsumerWidget { try { final client = ref.watch(apiClientProvider); final resp = await client.request( - name == null ? '/publishers' : '/publishers/$name', + name == null ? '/publishers/individual' : '/publishers/$name', data: { 'name': nameController.text, 'nick': nickController.text, diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index 5912a07..b3ee465 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -18,7 +18,9 @@ import 'package:island/services/file.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/widgets/post/publishers_modal.dart'; import 'package:lucide_icons/lucide_icons.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; @RoutePage() @@ -94,6 +96,7 @@ class PostComposeScreen extends HookConsumerWidget { final result = await ref .watch(imagePickerProvider) .pickMultiImage(requestFullMetadata: true); + if (result.isEmpty) return; attachments.value = [ ...attachments.value, ...result.map( @@ -106,6 +109,7 @@ class PostComposeScreen extends HookConsumerWidget { final result = await ref .watch(imagePickerProvider) .pickVideo(source: ImageSource.gallery); + if (result == null) return; attachments.value = [ ...attachments.value, UniversalFile(data: result, type: UniversalFileType.video), @@ -241,9 +245,19 @@ class PostComposeScreen extends HookConsumerWidget { spacing: 12, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ProfilePictureWidget( - item: currentPublisher.value?.picture, - radius: 24, + GestureDetector( + child: ProfilePictureWidget( + item: currentPublisher.value?.picture, + radius: 24, + ), + onTap: () { + showCupertinoModalBottomSheet( + context: context, + builder: (context) => PublisherModal(), + ).then((value) { + if (value is SnPublisher) currentPublisher.value = value; + }); + }, ).padding(top: 16), Expanded( child: SingleChildScrollView( diff --git a/lib/widgets/alert.dart b/lib/widgets/alert.dart index 9495c4c..8b29b56 100644 --- a/lib/widgets/alert.dart +++ b/lib/widgets/alert.dart @@ -1,10 +1,11 @@ -import 'dart:convert'; +import 'dart:developer'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_platform_alert/flutter_platform_alert.dart'; String _parseRemoteError(DioException err) { + log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); if (err.response?.data is String) return err.response?.data; if (err.response?.data?['errors'] != null) { final errors = err.response?.data['errors'] as Map; @@ -15,7 +16,7 @@ String _parseRemoteError(DioException err) { ) .join('\n'); } - return jsonEncode(err.response?.data); + return err.message ?? err.toString(); } void showErrorAlert(dynamic err) async { diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index f8d01db..a215f4c 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; @@ -16,7 +16,7 @@ import 'package:lucide_icons/lucide_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; -class PostItem extends ConsumerWidget { +class PostItem extends HookConsumerWidget { final SnPost item; final EdgeInsets? padding; final bool isOpenable; diff --git a/lib/widgets/post/post_quick_reply.dart b/lib/widgets/post/post_quick_reply.dart index 11bcc51..43386a4 100644 --- a/lib/widgets/post/post_quick_reply.dart +++ b/lib/widgets/post/post_quick_reply.dart @@ -7,7 +7,9 @@ import 'package:island/pods/network.dart'; import 'package:island/screens/account/me/publishers.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/widgets/post/publishers_modal.dart'; import 'package:lucide_icons/lucide_icons.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; class PostQuickReply extends HookConsumerWidget { @@ -62,9 +64,19 @@ class PostQuickReply extends HookConsumerWidget { (data) => Row( spacing: 8, children: [ - ProfilePictureWidget( - item: currentPublisher.value?.picture, - radius: 16, + GestureDetector( + child: ProfilePictureWidget( + item: currentPublisher.value?.picture, + radius: 16, + ), + onTap: () { + showCupertinoModalBottomSheet( + context: context, + builder: (context) => PublisherModal(), + ).then((value) { + if (value is SnPublisher) currentPublisher.value = value; + }); + }, ).padding(right: 4), Expanded( child: TextField( diff --git a/lib/widgets/post/publishers_modal.dart b/lib/widgets/post/publishers_modal.dart new file mode 100644 index 0000000..dfe2619 --- /dev/null +++ b/lib/widgets/post/publishers_modal.dart @@ -0,0 +1,91 @@ +import 'dart:math' as math; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/route.gr.dart'; +import 'package:island/screens/account/me/publishers.dart'; +import 'package:island/widgets/content/cloud_files.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class PublisherModal extends HookConsumerWidget { + const PublisherModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final publishers = ref.watch(publishersManagedProvider); + + return SizedBox( + height: math.min(MediaQuery.of(context).size.height * 0.4, 480), + child: Column( + children: [ + Expanded( + child: Material( + color: Colors.transparent, + child: publishers.when( + data: + (value) => + value.isEmpty + ? ConstrainedBox( + constraints: BoxConstraints(maxWidth: 280), + child: + Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'publishersEmpty', + textAlign: TextAlign.center, + ).tr().fontSize(17).bold(), + Text( + 'publishersEmptyDescription', + textAlign: TextAlign.center, + ).tr(), + const Gap(12), + ElevatedButton( + onPressed: () { + context.router + .push(NewPublisherRoute()) + .then((value) { + if (value != null) { + ref.invalidate( + publishersManagedProvider, + ); + } + }); + }, + child: Text('createPublisher').tr(), + ), + ], + ).center(), + ) + : SingleChildScrollView( + child: Column( + children: [ + for (final publisher in value) + ListTile( + leading: ProfilePictureWidget( + item: publisher.picture, + ), + title: Text(publisher.nick), + subtitle: Text('@${publisher.name}'), + onTap: () { + Navigator.pop(context, publisher); + }, + ), + ], + ), + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Text('Error: $e'), + ), + ), + ), + ], + ), + ); + } +}