Compare commits
	
		
			3 Commits
		
	
	
		
			a20c2598fc
			...
			594ac39e3d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 594ac39e3d | |||
| 23321171f3 | |||
| ee72d79c93 | 
| @@ -375,7 +375,9 @@ | |||||||
|   "postContent": "Content", |   "postContent": "Content", | ||||||
|   "postSettings": "Settings", |   "postSettings": "Settings", | ||||||
|   "postPublisherUnselected": "Publisher Unspecified", |   "postPublisherUnselected": "Publisher Unspecified", | ||||||
|   "postVisibility": "Visibility", |   "postType": "Post Type", | ||||||
|  |   "articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.", | ||||||
|  |   "postVisibility": "Post Visibility", | ||||||
|   "postVisibilityPublic": "Public", |   "postVisibilityPublic": "Public", | ||||||
|   "postVisibilityFriends": "Friends Only", |   "postVisibilityFriends": "Friends Only", | ||||||
|   "postVisibilityUnlisted": "Unlisted", |   "postVisibilityUnlisted": "Unlisted", | ||||||
|   | |||||||
| @@ -321,8 +321,15 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|             builder: (context, attachments, _) { |             builder: (context, attachments, _) { | ||||||
|               if (attachments.isEmpty) return const SizedBox.shrink(); |               if (attachments.isEmpty) return const SizedBox.shrink(); | ||||||
|               return Column( |               return Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                 children: [ |                 children: [ | ||||||
|                   const Gap(16), |                   const Gap(16), | ||||||
|  |                   Text( | ||||||
|  |                     'articleAttachmentHint'.tr(), | ||||||
|  |                     style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||||
|  |                       color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|  |                     ), | ||||||
|  |                   ).padding(bottom: 8), | ||||||
|                   ValueListenableBuilder<Map<int, double>>( |                   ValueListenableBuilder<Map<int, double>>( | ||||||
|                     valueListenable: state.attachmentProgress, |                     valueListenable: state.attachmentProgress, | ||||||
|                     builder: (context, progressMap, _) { |                     builder: (context, progressMap, _) { | ||||||
| @@ -332,8 +339,8 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|                         children: [ |                         children: [ | ||||||
|                           for (var idx = 0; idx < attachments.length; idx++) |                           for (var idx = 0; idx < attachments.length; idx++) | ||||||
|                             SizedBox( |                             SizedBox( | ||||||
|                               width: 120, |                               width: 280, | ||||||
|                               height: 120, |                               height: 280, | ||||||
|                               child: AttachmentPreview( |                               child: AttachmentPreview( | ||||||
|                                 item: attachments[idx], |                                 item: attachments[idx], | ||||||
|                                 progress: progressMap[idx], |                                 progress: progressMap[idx], | ||||||
| @@ -358,6 +365,12 @@ class ArticleComposeScreen extends HookConsumerWidget { | |||||||
|                                     delta, |                                     delta, | ||||||
|                                   ); |                                   ); | ||||||
|                                 }, |                                 }, | ||||||
|  |                                 onInsert: | ||||||
|  |                                     () => ComposeLogic.insertAttachment( | ||||||
|  |                                       ref, | ||||||
|  |                                       state, | ||||||
|  |                                       idx, | ||||||
|  |                                     ), | ||||||
|                               ), |                               ), | ||||||
|                             ), |                             ), | ||||||
|                         ], |                         ], | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ class AttachmentPreview extends StatelessWidget { | |||||||
|   final double? progress; |   final double? progress; | ||||||
|   final Function(int)? onMove; |   final Function(int)? onMove; | ||||||
|   final Function? onDelete; |   final Function? onDelete; | ||||||
|  |   final Function? onInsert; | ||||||
|   final Function? onRequestUpload; |   final Function? onRequestUpload; | ||||||
|   const AttachmentPreview({ |   const AttachmentPreview({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -23,6 +24,7 @@ class AttachmentPreview extends StatelessWidget { | |||||||
|     this.onRequestUpload, |     this.onRequestUpload, | ||||||
|     this.onMove, |     this.onMove, | ||||||
|     this.onDelete, |     this.onDelete, | ||||||
|  |     this.onInsert, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -104,7 +106,11 @@ class AttachmentPreview extends StatelessWidget { | |||||||
|                           style: TextStyle(color: Colors.white), |                           style: TextStyle(color: Colors.white), | ||||||
|                         ), |                         ), | ||||||
|                       Gap(6), |                       Gap(6), | ||||||
|                       Center(child: LinearProgressIndicator(value: progress)), |                       Center( | ||||||
|  |                         child: LinearProgressIndicator( | ||||||
|  |                           value: progress != null ? progress! / 100.0 : null, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|                     ], |                     ], | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
| @@ -166,6 +172,18 @@ class AttachmentPreview extends StatelessWidget { | |||||||
|                               onMove?.call(1); |                               onMove?.call(1); | ||||||
|                             }, |                             }, | ||||||
|                           ), |                           ), | ||||||
|  |                         if (onInsert != null) | ||||||
|  |                           InkWell( | ||||||
|  |                             borderRadius: BorderRadius.circular(8), | ||||||
|  |                             child: const Icon( | ||||||
|  |                               Symbols.add, | ||||||
|  |                               size: 14, | ||||||
|  |                               color: Colors.white, | ||||||
|  |                             ).padding(horizontal: 8, vertical: 6), | ||||||
|  |                             onTap: () { | ||||||
|  |                               onInsert?.call(); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|                       ], |                       ], | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:collection/collection.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| @@ -6,11 +7,14 @@ import 'package:flutter_highlight/themes/a11y-dark.dart'; | |||||||
| import 'package:flutter_highlight/themes/a11y-light.dart'; | import 'package:flutter_highlight/themes/a11y-light.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/content/markdown_latex.dart'; | import 'package:island/widgets/content/markdown_latex.dart'; | ||||||
| import 'package:markdown/markdown.dart' as markdown; | import 'package:markdown/markdown.dart' as markdown; | ||||||
| import 'package:markdown_widget/markdown_widget.dart'; | import 'package:markdown_widget/markdown_widget.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  |  | ||||||
| import 'image.dart'; | import 'image.dart'; | ||||||
| @@ -23,6 +27,7 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|   final TextStyle? linkStyle; |   final TextStyle? linkStyle; | ||||||
|   final EdgeInsets? linesMargin; |   final EdgeInsets? linesMargin; | ||||||
|   final bool isSelectable; |   final bool isSelectable; | ||||||
|  |   final List<SnCloudFile>? attachments; | ||||||
|  |  | ||||||
|   const MarkdownTextContent({ |   const MarkdownTextContent({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -33,6 +38,7 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|     this.linkStyle, |     this.linkStyle, | ||||||
|     this.isSelectable = false, |     this.isSelectable = false, | ||||||
|     this.linesMargin, |     this.linesMargin, | ||||||
|  |     this.attachments, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -109,6 +115,29 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|               final uri = Uri.parse(url); |               final uri = Uri.parse(url); | ||||||
|               if (uri.scheme == 'solian') { |               if (uri.scheme == 'solian') { | ||||||
|                 switch (uri.host) { |                 switch (uri.host) { | ||||||
|  |                   case 'files': | ||||||
|  |                     final file = attachments?.firstWhereOrNull( | ||||||
|  |                       (file) => file.id == uri.pathSegments[0], | ||||||
|  |                     ); | ||||||
|  |                     if (file == null) { | ||||||
|  |                       return const SizedBox.shrink(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     return ClipRRect( | ||||||
|  |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                       child: Container( | ||||||
|  |                         decoration: BoxDecoration( | ||||||
|  |                           color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                           borderRadius: const BorderRadius.all( | ||||||
|  |                             Radius.circular(8), | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         child: CloudFileWidget( | ||||||
|  |                           item: file, | ||||||
|  |                           fit: BoxFit.cover, | ||||||
|  |                         ).clipRRect(all: 8), | ||||||
|  |                       ), | ||||||
|  |                     ); | ||||||
|                   case 'stickers': |                   case 'stickers': | ||||||
|                     final size = doesEnlargeSticker ? 96.0 : 24.0; |                     final size = doesEnlargeSticker ? 96.0 : 24.0; | ||||||
|                     return ClipRRect( |                     return ClipRRect( | ||||||
| @@ -132,9 +161,9 @@ class MarkdownTextContent extends HookConsumerWidget { | |||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|               final content = UniversalImage( |               final content = ConstrainedBox( | ||||||
|                 uri: uri.toString(), |                 constraints: BoxConstraints(maxHeight: 360), | ||||||
|                 fit: BoxFit.cover, |                 child: UniversalImage(uri: uri.toString(), fit: BoxFit.contain), | ||||||
|               ); |               ); | ||||||
|               return content; |               return content; | ||||||
|             }, |             }, | ||||||
|   | |||||||
| @@ -474,6 +474,23 @@ class ComposeLogic { | |||||||
|     state.attachments.value = clone; |     state.attachments.value = clone; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   static void insertAttachment(WidgetRef ref, ComposeState state, int index) { | ||||||
|  |     final attachment = state.attachments.value[index]; | ||||||
|  |     if (!attachment.isOnCloud) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     final cloudFile = attachment.data as SnCloudFile; | ||||||
|  |     final markdown = ''; | ||||||
|  |     final controller = state.contentController; | ||||||
|  |     final text = controller.text; | ||||||
|  |     final selection = controller.selection; | ||||||
|  |     final newText = text.replaceRange(selection.start, selection.end, markdown); | ||||||
|  |     controller.text = newText; | ||||||
|  |     controller.selection = TextSelection.fromPosition( | ||||||
|  |       TextPosition(offset: selection.start + markdown.length), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   static Future<void> performAction( |   static Future<void> performAction( | ||||||
|     WidgetRef ref, |     WidgetRef ref, | ||||||
|     ComposeState state, |     ComposeState state, | ||||||
|   | |||||||
| @@ -163,7 +163,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|             if ((item.repliedPost != null || item.forwardedPost != null) && |             if ((item.repliedPost != null || item.forwardedPost != null) && | ||||||
|                 showReferencePost) |                 showReferencePost) | ||||||
|               _buildReferencePost(context, item), |               _buildReferencePost(context, item), | ||||||
|             if (item.attachments.isNotEmpty) |             if (item.attachments.isNotEmpty && item.type != 1) | ||||||
|               CloudFileList( |               CloudFileList( | ||||||
|                 files: item.attachments, |                 files: item.attachments, | ||||||
|                 maxWidth: math.min( |                 maxWidth: math.min( | ||||||
| @@ -331,6 +331,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|                                   item.type == 0 |                                   item.type == 0 | ||||||
|                                       ? EdgeInsets.only(bottom: 8) |                                       ? EdgeInsets.only(bottom: 8) | ||||||
|                                       : null, |                                       : null, | ||||||
|  |                               attachments: item.attachments, | ||||||
|                             ), |                             ), | ||||||
|                         ], |                         ], | ||||||
|                         // Render tags and categories if they exist |                         // Render tags and categories if they exist | ||||||
| @@ -389,7 +390,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|                                 item.forwardedPost != null) && |                                 item.forwardedPost != null) && | ||||||
|                             showReferencePost) |                             showReferencePost) | ||||||
|                           _buildReferencePost(context, item), |                           _buildReferencePost(context, item), | ||||||
|                         if (item.attachments.isNotEmpty) |                         if (item.attachments.isNotEmpty && item.type != 1) | ||||||
|                           CloudFileList( |                           CloudFileList( | ||||||
|                             files: item.attachments, |                             files: item.attachments, | ||||||
|                             maxWidth: math.min( |                             maxWidth: math.min( | ||||||
| @@ -689,6 +690,7 @@ Widget _buildReferencePost(BuildContext context, SnPost item) { | |||||||
|                           referencePost.type == 0 |                           referencePost.type == 0 | ||||||
|                               ? EdgeInsets.only(bottom: 4) |                               ? EdgeInsets.only(bottom: 4) | ||||||
|                               : null, |                               : null, | ||||||
|  |                       attachments: item.attachments, | ||||||
|                     ).padding(bottom: 4), |                     ).padding(bottom: 4), | ||||||
|                   // Truncation hint for referenced post |                   // Truncation hint for referenced post | ||||||
|                   if (referencePost.isTruncated) |                   if (referencePost.isTruncated) | ||||||
| @@ -696,7 +698,8 @@ Widget _buildReferencePost(BuildContext context, SnPost item) { | |||||||
|                       isCompact: true, |                       isCompact: true, | ||||||
|                       margin: const EdgeInsets.only(top: 4, bottom: 8), |                       margin: const EdgeInsets.only(top: 4, bottom: 8), | ||||||
|                     ), |                     ), | ||||||
|                   if (referencePost.attachments.isNotEmpty) |                   if (referencePost.attachments.isNotEmpty && | ||||||
|  |                       referencePost.type != 1) | ||||||
|                     Row( |                     Row( | ||||||
|                       mainAxisSize: MainAxisSize.min, |                       mainAxisSize: MainAxisSize.min, | ||||||
|                       children: [ |                       children: [ | ||||||
| @@ -1030,6 +1033,7 @@ class _ArticlePostDisplay extends StatelessWidget { | |||||||
|             MarkdownTextContent( |             MarkdownTextContent( | ||||||
|               content: item.content!, |               content: item.content!, | ||||||
|               textStyle: Theme.of(context).textTheme.bodyLarge, |               textStyle: Theme.of(context).textTheme.bodyLarge, | ||||||
|  |               attachments: item.attachments, | ||||||
|             ), |             ), | ||||||
|         ], |         ], | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/pods/userinfo.dart'; | ||||||
| import 'package:island/widgets/content/sheet.dart'; | import 'package:island/widgets/content/sheet.dart'; | ||||||
| import 'package:island/widgets/post/post_replies.dart'; | import 'package:island/widgets/post/post_replies.dart'; | ||||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | import 'package:island/widgets/post/post_quick_reply.dart'; | ||||||
| @@ -14,6 +15,8 @@ class PostRepliesSheet extends HookConsumerWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final user = ref.watch(userInfoProvider); | ||||||
|  |  | ||||||
|     return SheetScaffold( |     return SheetScaffold( | ||||||
|       titleText: 'repliesCount'.plural(post.repliesCount), |       titleText: 'repliesCount'.plural(post.repliesCount), | ||||||
|       child: Column( |       child: Column( | ||||||
| @@ -21,26 +24,29 @@ class PostRepliesSheet extends HookConsumerWidget { | |||||||
|           // Replies list |           // Replies list | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: CustomScrollView( |             child: CustomScrollView( | ||||||
|               slivers: [PostRepliesList( |               slivers: [ | ||||||
|                 postId: post.id.toString(), |                 PostRepliesList( | ||||||
|                 backgroundColor: Colors.transparent, |                   postId: post.id.toString(), | ||||||
|               )], |                   backgroundColor: Colors.transparent, | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           // Quick reply section |           // Quick reply section | ||||||
|           Material( |           if (user.value != null) | ||||||
|             elevation: 2, |             Material( | ||||||
|             child: PostQuickReply( |               elevation: 2, | ||||||
|               parent: post, |               child: PostQuickReply( | ||||||
|               onPosted: () { |                 parent: post, | ||||||
|                 ref.invalidate(postRepliesNotifierProvider(post.id)); |                 onPosted: () { | ||||||
|               }, |                   ref.invalidate(postRepliesNotifierProvider(post.id)); | ||||||
|             ).padding( |                 }, | ||||||
|               bottom: MediaQuery.of(context).padding.bottom + 16, |               ).padding( | ||||||
|               top: 16, |                 bottom: MediaQuery.of(context).padding.bottom + 16, | ||||||
|               horizontal: 16, |                 top: 16, | ||||||
|  |                 horizontal: 16, | ||||||
|  |               ), | ||||||
|             ), |             ), | ||||||
|           ), |  | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user