✨ Basic message editor
This commit is contained in:
		@@ -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..."
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,5 +33,6 @@
 | 
			
		||||
  "postDeleteConfirm": "你确定要删除这篇帖子吗?这意味着这个帖子将永远被我们丢弃在硬盘海中!该操作不可被反转!",
 | 
			
		||||
  "postEditNotify": "你正在修改一个已经发布了的帖子。",
 | 
			
		||||
  "reactionAdded": "你的反应已被添加。",
 | 
			
		||||
  "reactionRemoved": "你的反应已被移除。"
 | 
			
		||||
  "reactionRemoved": "你的反应已被移除。",
 | 
			
		||||
  "chatMessagePlaceholder": "发条消息……"
 | 
			
		||||
}
 | 
			
		||||
@@ -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<ChatScreen> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return IndentWrapper(
 | 
			
		||||
      noSafeArea: true,
 | 
			
		||||
      hideDrawer: true,
 | 
			
		||||
      title: _channelMeta?.name ?? "Loading...",
 | 
			
		||||
      child: PagedListView<int, Message>(
 | 
			
		||||
        pagingController: _pagingController,
 | 
			
		||||
        builderDelegate: PagedChildBuilderDelegate<Message>(
 | 
			
		||||
          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<int, Message>(
 | 
			
		||||
              pagingController: _pagingController,
 | 
			
		||||
              builderDelegate: PagedChildBuilderDelegate<Message>(
 | 
			
		||||
                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),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								lib/widgets/chat/message_editor.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								lib/widgets/chat/message_editor.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<ChatMessageEditor> createState() => _ChatMessageEditorState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ChatMessageEditorState extends State<ChatMessageEditor> {
 | 
			
		||||
  final _textController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  bool _isSubmitting = false;
 | 
			
		||||
 | 
			
		||||
  Future<void> sendMessage(BuildContext context) async {
 | 
			
		||||
    if (_isSubmitting) return;
 | 
			
		||||
 | 
			
		||||
    final auth = context.read<AuthProvider>();
 | 
			
		||||
    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(<String, dynamic>{
 | 
			
		||||
      '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),
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user