diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index c64b6cc..c4cfa51 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -33,5 +33,6 @@ "postDeleteConfirm": "Are you sure you want to delete this post? This operation cannot be revert!", "postEditNotify": "You are about editing a post that already published.", "reactionAdded": "Your reaction has been added.", - "reactionRemoved": "Your reaction has been removed." + "reactionRemoved": "Your reaction has been removed.", + "chatMessagePlaceholder": "Write a message..." } diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 0a260be..5bb4924 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -33,5 +33,6 @@ "postDeleteConfirm": "你确定要删除这篇帖子吗?这意味着这个帖子将永远被我们丢弃在硬盘海中!该操作不可被反转!", "postEditNotify": "你正在修改一个已经发布了的帖子。", "reactionAdded": "你的反应已被添加。", - "reactionRemoved": "你的反应已被移除。" + "reactionRemoved": "你的反应已被移除。", + "chatMessagePlaceholder": "发条消息……" } \ No newline at end of file diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 2590065..c3989b8 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:provider/provider.dart'; @@ -9,6 +10,7 @@ import 'package:solian/models/pagination.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/chat/message.dart'; +import 'package:solian/widgets/chat/message_editor.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:http/http.dart' as http; @@ -90,31 +92,37 @@ class _ChatScreenState extends State { @override Widget build(BuildContext context) { return IndentWrapper( - noSafeArea: true, hideDrawer: true, title: _channelMeta?.name ?? "Loading...", - child: PagedListView( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - bool isMerged = false, hasMerged = false; - if (index > 0) { - isMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item); - } - if (index + 1 < (_pagingController.itemList?.length ?? 0)) { - hasMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]); - } - return Container( - padding: EdgeInsets.only( - top: !isMerged ? 8 : 0, - bottom: !hasMerged ? 8 : 0, - left: 12, - right: 12, + child: Column( + children: [ + Expanded( + child: PagedListView( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + bool isMerged = false, hasMerged = false; + if (index > 0) { + isMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item); + } + if (index + 1 < (_pagingController.itemList?.length ?? 0)) { + hasMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]); + } + return Container( + padding: EdgeInsets.only( + top: !isMerged ? 8 : 0, + bottom: !hasMerged ? 8 : 0, + left: 12, + right: 12, + ), + child: ChatMessage(item: item, underMerged: isMerged), + ); + }, ), - child: ChatMessage(item: item, underMerged: isMerged), - ); - }, - ), + ), + ), + ChatMessageEditor(channel: widget.alias), + ], ), ); } diff --git a/lib/widgets/chat/message_editor.dart b/lib/widgets/chat/message_editor.dart new file mode 100644 index 0000000..7b2f5af --- /dev/null +++ b/lib/widgets/chat/message_editor.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:http/http.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/models/message.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/utils/service_url.dart'; + +class ChatMessageEditor extends StatefulWidget { + final String channel; + final Message? editing; + + const ChatMessageEditor({super.key, required this.channel, this.editing}); + + @override + State createState() => _ChatMessageEditorState(); +} + +class _ChatMessageEditorState extends State { + final _textController = TextEditingController(); + + bool _isSubmitting = false; + + Future sendMessage(BuildContext context) async { + if (_isSubmitting) return; + + final auth = context.read(); + if (!await auth.isAuthorized()) return; + + final uri = widget.editing == null + ? getRequestUri('messaging', '/api/channels/${widget.channel}/messages') + : getRequestUri('messaging', '/api/channels/${widget.channel}/messages/${widget.editing!.id}'); + + final req = Request(widget.editing == null ? "POST" : "PUT", uri); + req.headers['Content-Type'] = 'application/json'; + req.body = jsonEncode({ + 'content': _textController.value.text, + }); + + setState(() => _isSubmitting = true); + 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 { + reset(); + } + setState(() => _isSubmitting = false); + } + + void reset() { + _textController.clear(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 56, + padding: const EdgeInsets.all(12), + decoration: const BoxDecoration( + border: Border( + top: BorderSide(width: 0.3, color: Color(0xffdedede)), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: TextField( + controller: _textController, + maxLines: null, + autofocus: true, + autocorrect: true, + keyboardType: TextInputType.text, + decoration: InputDecoration.collapsed( + hintText: AppLocalizations.of(context)!.chatMessagePlaceholder, + ), + onSubmitted: (_) => sendMessage(context), + ), + ), + TextButton( + style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)), + onPressed: !_isSubmitting ? () => sendMessage(context) : null, + child: const Icon(Icons.send), + ) + ], + ), + ); + } +}