From d8cd33e79a669946713fa098b55651246ac36842 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 28 Jul 2025 02:01:36 +0800 Subject: [PATCH] :sparkles: Add file from ID :bug: Fix explore unauthorized render error on large screen --- assets/i18n/en-US.json | 4 ++ assets/i18n/zh-CN.json | 4 ++ assets/i18n/zh-TW.json | 4 ++ lib/screens/explore.dart | 78 ++++++++++++++++-------- lib/screens/posts/compose.dart | 5 ++ lib/widgets/post/compose_shared.dart | 88 ++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 26 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 70314b0..6db88ba 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -147,6 +147,10 @@ "addVideo": "Add video", "addPhoto": "Add photo", "addFile": "Add file", + "addAttachmentById": "Add Attachment by ID", + "enterFileId": "Enter File ID", + "fileIdCannotBeEmpty": "File ID cannot be empty", + "failedToFetchFile": "Failed to fetch file: {}", "createDirectMessage": "Send new DM", "gotoDirectMessage": "Go to DM", "react": "React", diff --git a/assets/i18n/zh-CN.json b/assets/i18n/zh-CN.json index 49357d3..0e821eb 100644 --- a/assets/i18n/zh-CN.json +++ b/assets/i18n/zh-CN.json @@ -123,6 +123,10 @@ "addVideo": "添加视频", "addPhoto": "添加照片", "addFile": "添加文件", + "addAttachmentById": "通过 ID 添加附件", + "enterFileId": "输入文件 ID", + "fileIdCannotBeEmpty": "文件 ID 不能为空", + "failedToFetchFile": "获取文件失败: {}", "createDirectMessage": "创建新私人消息", "gotoDirectMessage": "前往私信", "react": "反应", diff --git a/assets/i18n/zh-TW.json b/assets/i18n/zh-TW.json index c258aa1..26bd8c4 100644 --- a/assets/i18n/zh-TW.json +++ b/assets/i18n/zh-TW.json @@ -123,6 +123,10 @@ "addVideo": "新增影片", "addPhoto": "新增照片", "addFile": "新增檔案", + "addAttachmentById": "透過 ID 新增附件", + "enterFileId": "輸入檔案 ID", + "fileIdCannotBeEmpty": "檔案 ID 不能為空", + "failedToFetchFile": "無法取得檔案: {}", "createDirectMessage": "建立新私人訊息", "gotoDirectMessage": "Go to DM", "react": "反應", diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 37c9f37..8a2654f 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -84,6 +84,8 @@ class ExploreScreen extends HookConsumerWidget { selectedDay.value = day; } + final user = ref.watch(userInfoProvider); + return AppScaffold( noBackground: false, appBar: AppBar( @@ -198,36 +200,60 @@ class ExploreScreen extends HookConsumerWidget { children: [ Flexible(flex: 3, child: bodyView), const VerticalDivider(width: 1), - Flexible( - flex: 2, - child: SingleChildScrollView( - child: Column( - children: [ - CheckInWidget(), - Card( - margin: EdgeInsets.only(left: 16, right: 16, top: 8), - child: Column( - children: [ - // Use the reusable EventCalendarWidget - EventCalendarWidget( - events: events, - initialDate: now, - showEventDetails: true, - onMonthChanged: onMonthChanged, - onDaySelected: onDaySelected, - ), - ], + if (user.value != null) + Flexible( + flex: 2, + child: SingleChildScrollView( + child: Column( + children: [ + CheckInWidget(), + Card( + margin: EdgeInsets.only( + left: 16, + right: 16, + top: 8, + ), + child: Column( + children: [ + // Use the reusable EventCalendarWidget + EventCalendarWidget( + events: events, + initialDate: now, + showEventDetails: true, + onMonthChanged: onMonthChanged, + onDaySelected: onDaySelected, + ), + ], + ), ), - ), - FortuneGraphWidget( - events: events, - constrainWidth: true, - onPointSelected: onDaySelected, + FortuneGraphWidget( + events: events, + constrainWidth: true, + onPointSelected: onDaySelected, + ), + ], + ), + ), + ) + else + Flexible( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome to\nthe Solar Network', + style: Theme.of(context).textTheme.titleLarge, + ).bold(), + const Gap(2), + Text( + 'Login to explore more!', + style: Theme.of(context).textTheme.bodyLarge, ), ], - ), + ).padding(horizontal: 36, vertical: 16), ), - ), ], ); } diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index 00911cf..c9cced5 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -469,6 +469,11 @@ class PostComposeScreen extends HookConsumerWidget { icon: const Icon(Symbols.videocam), color: colorScheme.primary, ), + IconButton( + onPressed: () => ComposeLogic.addAttachmentById(ref, state, context), + icon: const Icon(Symbols.attach_file), + color: colorScheme.primary, + ), ], ).padding( bottom: MediaQuery.of(context).padding.bottom + 16, diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index b582894..be5d19d 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -392,6 +392,94 @@ class ComposeLogic { ]; } + static Future addAttachmentById( + WidgetRef ref, + ComposeState state, + BuildContext context, + ) async { + final TextEditingController idController = TextEditingController(); + String? errorMessage; + + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: Text('addAttachmentById'.tr()), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: idController, + decoration: InputDecoration( + hintText: 'enterFileId'.tr(), + errorText: errorMessage, + ), + ), + ], + ), + actions: [ + TextButton( + child: Text('cancel'.tr()), + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + ), + TextButton( + child: Text('add'.tr()), + onPressed: () async { + final fileId = idController.text.trim(); + if (fileId.isEmpty) { + setState(() { + errorMessage = 'fileIdCannotBeEmpty'.tr(); + }); + return; + } + + try { + final client = ref.read(apiClientProvider); + final response = await client.get( + '/drive/files/$fileId/info', + ); + final SnCloudFile cloudFile = SnCloudFile.fromJson( + response.data, + ); + + state.attachments.value = [ + ...state.attachments.value, + UniversalFile( + data: cloudFile, + type: switch (cloudFile.mimeType + ?.split('/') + .firstOrNull) { + 'image' => UniversalFileType.image, + 'video' => UniversalFileType.video, + 'audio' => UniversalFileType.audio, + _ => UniversalFileType.file, + }, + ), + ]; + if (context.mounted) { + Navigator.of(dialogContext).pop(); + } + } catch (e) { + setState(() { + errorMessage = 'failedToFetchFile'.tr( + args: [e.toString()], + ); + }); + } + }, + ), + ], + ); + }, + ); + }, + ); + } + static Future uploadAttachment( WidgetRef ref, ComposeState state,