✨ Channel creation and management
This commit is contained in:
		| @@ -12,11 +12,13 @@ | ||||
|   "confirmCancel": "Not sure", | ||||
|   "confirmOkay": "OK", | ||||
|   "edit": "Edit", | ||||
|   "apply": "Apply", | ||||
|   "delete": "Delete", | ||||
|   "action": "Action", | ||||
|   "cancel": "Cancel", | ||||
|   "report": "Report", | ||||
|   "reply": "Reply", | ||||
|   "settings": "Settings", | ||||
|   "reaction": "Reaction", | ||||
|   "reactVerb": "React", | ||||
|   "post": "Post", | ||||
| @@ -40,6 +42,13 @@ | ||||
|   "chatNew": "New Chat", | ||||
|   "chatNewCreate": "Create a channel", | ||||
|   "chatNewJoin": "Join a exists channel", | ||||
|   "chatChannelUsage": "Channel", | ||||
|   "chatChannelUsageCaption": "Channel is place to talk with people, one or a lot.", | ||||
|   "chatChannelOrganize": "Organize a channel", | ||||
|   "chatChannelEditNotify": "You are about editing a existing channel.", | ||||
|   "chatChannelAliasLabel": "Channel Alias", | ||||
|   "chatChannelNameLabel": "Channel Name", | ||||
|   "chatChannelDescriptionLabel": "Channel Description", | ||||
|   "chatMessagePlaceholder": "Write a message...", | ||||
|   "chatMessageEditNotify": "You are about editing a message.", | ||||
|   "chatMessageReplyNotify": "You are about replying a message.", | ||||
|   | ||||
| @@ -14,9 +14,11 @@ | ||||
|   "edit": "编辑", | ||||
|   "delete": "删除", | ||||
|   "action": "操作", | ||||
|   "apply": "应用", | ||||
|   "cancel": "取消", | ||||
|   "report": "举报", | ||||
|   "reply": "回复", | ||||
|   "settings": "设置", | ||||
|   "reaction": "反应", | ||||
|   "reactVerb": "作出反应", | ||||
|   "post": "帖子", | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/chat/chat.dart'; | ||||
| @@ -7,6 +8,7 @@ import 'package:solian/screens/explore.dart'; | ||||
| import 'package:solian/screens/posts/comment_editor.dart'; | ||||
| import 'package:solian/screens/posts/moment_editor.dart'; | ||||
| import 'package:solian/screens/posts/screen.dart'; | ||||
| import 'package:solian/widgets/chat/channel_editor.dart'; | ||||
|  | ||||
| final router = GoRouter( | ||||
|   routes: [ | ||||
| @@ -21,7 +23,12 @@ final router = GoRouter( | ||||
|       builder: (context, state) => const ChatIndexScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/:channel', | ||||
|       path: '/chat/create', | ||||
|       name: 'chat.channel.editor', | ||||
|       builder: (context, state) => ChannelEditor(editing: state.extra as Channel?), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/c/:channel', | ||||
|       name: 'chat.channel', | ||||
|       builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String), | ||||
|     ), | ||||
| @@ -33,16 +40,14 @@ final router = GoRouter( | ||||
|     GoRoute( | ||||
|       path: '/posts/publish/moments', | ||||
|       name: 'posts.moments.editor', | ||||
|       builder: (context, state) => | ||||
|           MomentEditorScreen(editing: state.extra as Post?), | ||||
|       builder: (context, state) => MomentEditorScreen(editing: state.extra as Post?), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/posts/publish/comments', | ||||
|       name: 'posts.comments.editor', | ||||
|       builder: (context, state) { | ||||
|         final args = state.extra as CommentPostArguments; | ||||
|         return CommentEditorScreen( | ||||
|             editing: args.editing, related: args.related); | ||||
|         return CommentEditorScreen(editing: args.editing, related: args.related); | ||||
|       }, | ||||
|     ), | ||||
|     GoRoute( | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/chat/channel_action.dart'; | ||||
| import 'package:solian/widgets/chat/maintainer.dart'; | ||||
| import 'package:solian/widgets/chat/message.dart'; | ||||
| import 'package:solian/widgets/chat/message_action.dart'; | ||||
| @@ -31,7 +32,7 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|  | ||||
|   final http.Client _client = http.Client(); | ||||
|  | ||||
|   Future<void> fetchMetadata(BuildContext context) async { | ||||
|   Future<void> fetchMetadata() async { | ||||
|     var uri = getRequestUri('messaging', '/api/channels/${widget.alias}'); | ||||
|     var res = await _client.get(uri); | ||||
|     if (res.statusCode == 200) { | ||||
| @@ -120,7 +121,7 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     Future.delayed(Duration.zero, () { | ||||
|       fetchMetadata(context); | ||||
|       fetchMetadata(); | ||||
|     }); | ||||
|  | ||||
|     _pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context)); | ||||
| @@ -133,6 +134,9 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|     return IndentWrapper( | ||||
|       hideDrawer: true, | ||||
|       title: _channelMeta?.name ?? "Loading...", | ||||
|       appBarActions: [ | ||||
|         _channelMeta != null ? ChannelAction(channel: _channelMeta!, onUpdate: () => fetchMetadata()) : Container(), | ||||
|       ], | ||||
|       child: ChatMaintainer( | ||||
|         child: Column( | ||||
|           children: [ | ||||
| @@ -141,6 +145,7 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|                 reverse: true, | ||||
|                 pagingController: _pagingController, | ||||
|                 builderDelegate: PagedChildBuilderDelegate<Message>( | ||||
|                   noItemsFoundIndicatorBuilder: (_) => Container(), | ||||
|                   itemBuilder: (context, item, index) { | ||||
|                     bool isMerged = false, hasMerged = false; | ||||
|                     if (index > 0) { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ class ChatIndexScreen extends StatefulWidget { | ||||
| class _ChatIndexScreenState extends State<ChatIndexScreen> { | ||||
|   List<Channel> _channels = List.empty(); | ||||
|  | ||||
|   Future<void> fetchChannels(BuildContext context) async { | ||||
|   Future<void> fetchChannels() async { | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     if (!await auth.isAuthorized()) return; | ||||
|  | ||||
| @@ -44,14 +44,14 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> { | ||||
|   void viewNewChatAction() { | ||||
|     showModalBottomSheet( | ||||
|       context: context, | ||||
|       builder: (context) => const ChatNewAction(), | ||||
|       builder: (context) => ChatNewAction(onUpdate: () => fetchChannels()), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     Future.delayed(Duration.zero, () { | ||||
|       fetchChannels(context); | ||||
|       fetchChannels(); | ||||
|     }); | ||||
|  | ||||
|     super.initState(); | ||||
| @@ -85,7 +85,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> { | ||||
|             } | ||||
|  | ||||
|             return RefreshIndicator( | ||||
|               onRefresh: () => fetchChannels(context), | ||||
|               onRefresh: () => fetchChannels(), | ||||
|               child: ListView.builder( | ||||
|                 itemCount: _channels.length, | ||||
|                 itemBuilder: (context, index) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| @@ -48,8 +49,13 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|   } | ||||
|  | ||||
|   Future<void> applyPost(BuildContext context) async { | ||||
|     setState(() => _isSubmitting = true); | ||||
|  | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     if (!await auth.isAuthorized()) return; | ||||
|     if (!await auth.isAuthorized()) { | ||||
|       setState(() => _isSubmitting = false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     final alias = widget.related?.alias ?? 'not-found'; | ||||
|     final relatedDataset = '${widget.related?.modelType ?? 'comment'}s'; | ||||
| @@ -65,7 +71,6 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|       'attachments': _attachments, | ||||
|     }); | ||||
|  | ||||
|     setState(() => _isSubmitting = true); | ||||
|     var res = await Response.fromStream(await auth.client!.send(req)); | ||||
|     if (res.statusCode != 200) { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
| @@ -81,8 +86,8 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|   } | ||||
|  | ||||
|   void cancelEditing() { | ||||
|     if (Navigator.canPop(context)) { | ||||
|       Navigator.pop(context); | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -129,7 +134,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|           constraints: const BoxConstraints(maxWidth: 640), | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               _isSubmitting ? const LinearProgressIndicator() : Container(), | ||||
|               _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|               FutureBuilder( | ||||
|                 future: auth.getProfiles(), | ||||
|                 builder: (context, snapshot) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| @@ -40,8 +41,13 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|   } | ||||
|  | ||||
|   Future<void> applyPost(BuildContext context) async { | ||||
|     setState(() => _isSubmitting = true); | ||||
|  | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     if (!await auth.isAuthorized()) return; | ||||
|     if (!await auth.isAuthorized()) { | ||||
|       setState(() => _isSubmitting = false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     final uri = widget.editing == null | ||||
|         ? getRequestUri('interactive', '/api/p/moments') | ||||
| @@ -55,7 +61,6 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|       'attachments': _attachments, | ||||
|     }); | ||||
|  | ||||
|     setState(() => _isSubmitting = true); | ||||
|     var res = await Response.fromStream(await auth.client!.send(req)); | ||||
|     if (res.statusCode != 200) { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
| @@ -71,8 +76,8 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|   } | ||||
|  | ||||
|   void cancelEditing() { | ||||
|     if (Navigator.canPop(context)) { | ||||
|       Navigator.pop(context); | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -119,7 +124,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|           constraints: const BoxConstraints(maxWidth: 640), | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               _isSubmitting ? const LinearProgressIndicator() : Container(), | ||||
|               _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|               FutureBuilder( | ||||
|                 future: auth.getProfiles(), | ||||
|                 builder: (context, snapshot) { | ||||
|   | ||||
							
								
								
									
										53
									
								
								lib/widgets/chat/channel_action.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/widgets/chat/channel_action.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
|  | ||||
| class ChannelAction extends StatelessWidget { | ||||
|   final Channel channel; | ||||
|   final Function onUpdate; | ||||
|  | ||||
|   ChannelAction({super.key, required this.channel, required this.onUpdate}); | ||||
|  | ||||
|   final FocusNode _focusNode = FocusNode(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       children: [ | ||||
|         MenuAnchor( | ||||
|           menuChildren: [ | ||||
|             MenuItemButton( | ||||
|               child: Row( | ||||
|                 children: [ | ||||
|                   const Icon(Icons.settings), | ||||
|                   const SizedBox(width: 12), | ||||
|                   Text(AppLocalizations.of(context)!.settings), | ||||
|                 ], | ||||
|               ), | ||||
|               onPressed: () { | ||||
|                 router.pushNamed('chat.channel.editor', extra: channel).then((did) { | ||||
|                   if(did == true) onUpdate(); | ||||
|                 }); | ||||
|               }, | ||||
|             ), | ||||
|           ], | ||||
|           builder: (BuildContext context, MenuController controller, Widget? child) { | ||||
|             return IconButton( | ||||
|               onPressed: () { | ||||
|                 if (controller.isOpen) { | ||||
|                   controller.close(); | ||||
|                 } else { | ||||
|                   controller.open(); | ||||
|                 } | ||||
|               }, | ||||
|               focusNode: _focusNode, | ||||
|               style: TextButton.styleFrom(shape: const CircleBorder()), | ||||
|               icon: const Icon(Icons.more_horiz), | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										188
									
								
								lib/widgets/chat/channel_editor.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								lib/widgets/chat/channel_editor.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| class ChannelEditor extends StatefulWidget { | ||||
|   final Channel? editing; | ||||
|  | ||||
|   const ChannelEditor({super.key, this.editing}); | ||||
|  | ||||
|   @override | ||||
|   State<ChannelEditor> createState() => _ChannelEditorState(); | ||||
| } | ||||
|  | ||||
| class _ChannelEditorState extends State<ChannelEditor> { | ||||
|   final _aliasController = TextEditingController(); | ||||
|   final _nameController = TextEditingController(); | ||||
|   final _descriptionController = TextEditingController(); | ||||
|  | ||||
|   bool _isSubmitting = false; | ||||
|  | ||||
|   Future<void> applyChannel(BuildContext context) async { | ||||
|     setState(() => _isSubmitting = true); | ||||
|  | ||||
|     final auth = context.read<AuthProvider>(); | ||||
|     if (!await auth.isAuthorized()) { | ||||
|       setState(() => _isSubmitting = false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     final uri = widget.editing == null | ||||
|         ? getRequestUri('messaging', '/api/channels') | ||||
|         : getRequestUri('messaging', '/api/channels/${widget.editing!.id}'); | ||||
|  | ||||
|     final req = Request(widget.editing == null ? "POST" : "PUT", uri); | ||||
|     req.headers['Content-Type'] = 'application/json'; | ||||
|     req.body = jsonEncode(<String, dynamic>{ | ||||
|       'alias': _aliasController.value.text.toLowerCase(), | ||||
|       'name': _nameController.value.text, | ||||
|       'description': _descriptionController.value.text, | ||||
|     }); | ||||
|  | ||||
|     var res = await Response.fromStream(await auth.client!.send(req)); | ||||
|     if (res.statusCode != 200) { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       ScaffoldMessenger.of(context).showSnackBar( | ||||
|         SnackBar(content: Text("Something went wrong... $message")), | ||||
|       ); | ||||
|     } else { | ||||
|       if (router.canPop()) { | ||||
|         router.pop(true); | ||||
|       } | ||||
|     } | ||||
|     setState(() => _isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   void randomizeAlias() { | ||||
|     _aliasController.text = const Uuid().v4().replaceAll('-', ''); | ||||
|   } | ||||
|  | ||||
|   void cancelEditing() { | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     if (widget.editing != null) { | ||||
|       _aliasController.text = widget.editing!.alias; | ||||
|       _nameController.text = widget.editing!.name; | ||||
|       _descriptionController.text = widget.editing!.description; | ||||
|     } | ||||
|  | ||||
|     super.initState(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final editingBanner = MaterialBanner( | ||||
|       padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), | ||||
|       leading: const Icon(Icons.edit_note), | ||||
|       backgroundColor: const Color(0xFFE0E0E0), | ||||
|       dividerColor: const Color.fromARGB(1, 0, 0, 0), | ||||
|       content: Text(AppLocalizations.of(context)!.chatChannelEditNotify), | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|           child: Text(AppLocalizations.of(context)!.cancel), | ||||
|           onPressed: () => cancelEditing(), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|  | ||||
|     return IndentWrapper( | ||||
|       hideDrawer: true, | ||||
|       title: AppLocalizations.of(context)!.chatChannelOrganize, | ||||
|       appBarActions: <Widget>[ | ||||
|         TextButton( | ||||
|           onPressed: !_isSubmitting ? () => applyChannel(context) : null, | ||||
|           child: Text(AppLocalizations.of(context)!.apply.toUpperCase()), | ||||
|         ), | ||||
|       ], | ||||
|       child: Center( | ||||
|         child: Container( | ||||
|           constraints: const BoxConstraints(maxWidth: 640), | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|               ListTile( | ||||
|                 title: Text(AppLocalizations.of(context)!.chatChannelUsage), | ||||
|                 subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption), | ||||
|                 leading: const CircleAvatar( | ||||
|                   backgroundColor: Colors.teal, | ||||
|                   child: Icon(Icons.tag, color: Colors.white), | ||||
|                 ), | ||||
|               ), | ||||
|               const Divider(thickness: 0.3), | ||||
|               Container( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2), | ||||
|                 child: Row( | ||||
|                   children: [ | ||||
|                     Expanded( | ||||
|                       child: TextField( | ||||
|                         autofocus: true, | ||||
|                         controller: _aliasController, | ||||
|                         decoration: InputDecoration.collapsed( | ||||
|                           hintText: AppLocalizations.of(context)!.chatChannelAliasLabel, | ||||
|                         ), | ||||
|                         onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                       ), | ||||
|                     ), | ||||
|                     TextButton( | ||||
|                       style: TextButton.styleFrom( | ||||
|                         shape: const CircleBorder(), | ||||
|                         visualDensity: const VisualDensity(horizontal: -2, vertical: -2), | ||||
|                       ), | ||||
|                       onPressed: () => randomizeAlias(), | ||||
|                       child: const Icon(Icons.refresh), | ||||
|                     ) | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               const Divider(thickness: 0.3), | ||||
|               Container( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | ||||
|                 child: TextField( | ||||
|                   autocorrect: true, | ||||
|                   controller: _nameController, | ||||
|                   decoration: InputDecoration.collapsed( | ||||
|                     hintText: AppLocalizations.of(context)!.chatChannelNameLabel, | ||||
|                   ), | ||||
|                   onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                 ), | ||||
|               ), | ||||
|               const Divider(thickness: 0.3), | ||||
|               Expanded( | ||||
|                 child: Container( | ||||
|                   padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | ||||
|                   child: TextField( | ||||
|                     minLines: 5, | ||||
|                     maxLines: null, | ||||
|                     autocorrect: true, | ||||
|                     keyboardType: TextInputType.multiline, | ||||
|                     controller: _descriptionController, | ||||
|                     decoration: InputDecoration.collapsed( | ||||
|                       hintText: AppLocalizations.of(context)!.chatChannelDescriptionLabel, | ||||
|                     ), | ||||
|                     onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|               widget.editing != null ? editingBanner : Container(), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,9 +1,11 @@ | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
|  | ||||
| class ChatNewAction extends StatelessWidget { | ||||
|   const ChatNewAction({super.key}); | ||||
|   final Function onUpdate; | ||||
|  | ||||
|   const ChatNewAction({super.key, required this.onUpdate}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -26,6 +28,16 @@ class ChatNewAction extends StatelessWidget { | ||||
|                 ListTile( | ||||
|                   leading: const Icon(Icons.add), | ||||
|                   title: Text(AppLocalizations.of(context)!.chatNewCreate), | ||||
|                   onTap: () { | ||||
|                     router.pushNamed('chat.channel.editor').then((did) { | ||||
|                       if (did == true) { | ||||
|                         onUpdate(); | ||||
|                         if (Navigator.canPop(context)) { | ||||
|                           Navigator.pop(context); | ||||
|                         } | ||||
|                       } | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|                 ListTile( | ||||
|                   leading: const Icon(Icons.travel_explore), | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import 'dart:math' as math; | ||||
|  | ||||
| import 'package:crypto/crypto.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| @@ -184,7 +185,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> { | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Container( | ||||
|           padding: const EdgeInsets.only(left: 8, right: 8, top: 20), | ||||
|           padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), | ||||
|           child: Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|             children: [ | ||||
| @@ -215,7 +216,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> { | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         _isSubmitting ? const LinearProgressIndicator() : Container(), | ||||
|         _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), | ||||
|         Expanded( | ||||
|           child: ListView.separated( | ||||
|             itemCount: _attachments.length, | ||||
|   | ||||
							
								
								
									
										7
									
								
								manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								manifest.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| { | ||||
|   "version": "1.0.0", | ||||
|   "release": { | ||||
|     "title": "The initial release", | ||||
|     "description": "We just moved our frontend technology stack to flutter! It makes a lot of fun and good performance. Now enjoy the Solar Network with seamless experience!" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -182,6 +182,14 @@ packages: | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   flutter_animate: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_animate | ||||
|       sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.5.0" | ||||
|   flutter_carousel_widget: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -275,6 +283,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.0" | ||||
|   flutter_shaders: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_shaders | ||||
|       sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.1.2" | ||||
|   flutter_staggered_grid_view: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -60,6 +60,7 @@ dependencies: | ||||
|   flutter_launcher_icons: ^0.13.1 | ||||
|   web_socket_channel: ^2.4.5 | ||||
|   badges: ^3.1.2 | ||||
|   flutter_animate: ^4.5.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user