import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/reply_type.dart'; import 'package:PiliPlus/models/video/reply/emote.dart'; import 'package:PiliPlus/models/video/reply/item.dart'; import 'package:PiliPlus/pages/emote/index.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; import 'toolbar_icon_button.dart'; class VideoReplyNewDialog extends StatefulWidget { final int? oid; final int? root; final int? parent; final ReplyType? replyType; final ReplyItemModel? replyItem; const VideoReplyNewDialog({ super.key, this.oid, this.root, this.parent, this.replyType, this.replyItem, }); @override State createState() => _VideoReplyNewDialogState(); } class _VideoReplyNewDialogState extends State with WidgetsBindingObserver { final TextEditingController _replyContentController = TextEditingController(); final FocusNode replyContentFocusNode = FocusNode(); final GlobalKey _formKey = GlobalKey(); late double emoteHeight = 0.0; double keyboardHeight = 0.0; // 键盘高度 final _debouncer = Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 String toolbarType = 'input'; bool _enablePublish = false; final _publishStream = StreamController(); @override void initState() { super.initState(); // 监听输入框聚焦 // replyContentFocusNode.addListener(_onFocus); // 界面观察者 必须 WidgetsBinding.instance.addObserver(this); // 自动聚焦 _autoFocus(); // 监听聚焦状态 _focusListener(); } _autoFocus() async { await Future.delayed(const Duration(milliseconds: 300)); if (context.mounted) { FocusScope.of(context).requestFocus(replyContentFocusNode); } } _focusListener() { replyContentFocusNode.addListener(() { if (replyContentFocusNode.hasFocus) { setState(() { toolbarType = 'input'; }); } }); } Future submitReplyAdd() async { feedBack(); String message = _replyContentController.text; var result = await VideoHttp.replyAdd( type: widget.replyType ?? ReplyType.video, oid: widget.oid!, root: widget.root!, parent: widget.parent!, message: widget.replyItem != null && widget.replyItem!.root != 0 ? ' 回复 @${widget.replyItem!.member!.uname!} : $message' : message, ); if (result['status']) { SmartDialog.showToast(result['data']['success_toast']); Get.back(result: { 'data': ReplyItemModel.fromJson(result['data']['reply'], ''), }); } else { SmartDialog.showToast(result['msg']); } } void onChooseEmote(Packages package, Emote emote) { if (!_enablePublish) { _enablePublish = true; _publishStream.add(true); } final int cursorPosition = _replyContentController.selection.baseOffset; final String currentText = _replyContentController.text; final String newText = currentText.substring(0, cursorPosition) + emote.text! + currentText.substring(cursorPosition); _replyContentController.value = TextEditingValue( text: newText, selection: TextSelection.collapsed(offset: cursorPosition + emote.text!.length), ); } @override void didChangeMetrics() { super.didChangeMetrics(); if (!mounted) return; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; // 键盘高度 final viewInsets = EdgeInsets.fromViewPadding( View.of(context).viewInsets, View.of(context).devicePixelRatio); _debouncer(() { if (!mounted) return; if (keyboardHeight == 0 && emoteHeight == 0) { emoteHeight = keyboardHeight = keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight; if (emoteHeight < 200) emoteHeight = 200; setState(() {}); } }); }); } @override void dispose() { _publishStream.close(); WidgetsBinding.instance.removeObserver(this); _replyContentController.dispose(); replyContentFocusNode.removeListener(() {}); replyContentFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { double _keyboardHeight = EdgeInsets.fromViewPadding( View.of(context).viewInsets, View.of(context).devicePixelRatio) .bottom; return Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), color: Theme.of(context).colorScheme.surface, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.only(top: 12, right: 15, left: 15, bottom: 10), child: SingleChildScrollView( child: Form( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, child: TextField( controller: _replyContentController, minLines: 4, maxLines: 8, autofocus: false, onChanged: (value) { if (value.isNotEmpty && !_enablePublish) { _enablePublish = true; _publishStream.add(true); } else if (value.isEmpty && _enablePublish) { _enablePublish = false; _publishStream.add(false); } }, focusNode: replyContentFocusNode, decoration: const InputDecoration( hintText: "输入回复内容", border: InputBorder.none, hintStyle: TextStyle( fontSize: 14, )), style: Theme.of(context).textTheme.bodyLarge, ), ), ), ), Divider( height: 1, color: Theme.of(context).dividerColor.withOpacity(0.1), ), Container( height: 52, padding: const EdgeInsets.only(left: 12, right: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ToolbarIconButton( tooltip: '输入', onPressed: () { if (toolbarType == 'emote') { setState(() { toolbarType = 'input'; }); } FocusScope.of(context).requestFocus(replyContentFocusNode); }, icon: const Icon(Icons.keyboard, size: 22), // toolbarType: toolbarType, selected: toolbarType == 'input', ), const SizedBox(width: 20), ToolbarIconButton( tooltip: '表情', onPressed: () { if (toolbarType == 'input') { setState(() { toolbarType = 'emote'; }); } FocusScope.of(context).unfocus(); }, icon: const Icon(Icons.emoji_emotions, size: 22), // toolbarType: toolbarType, selected: toolbarType == 'emote', ), const Spacer(), StreamBuilder( initialData: false, stream: _publishStream.stream, builder: (context, snapshot) => FilledButton.tonal( onPressed: snapshot.data == true ? submitReplyAdd : null, style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), visualDensity: const VisualDensity( horizontal: -2, vertical: -2, ), ), child: const Text('发送'), ), ), ], ), ), AnimatedSize( curve: Curves.easeInOut, duration: const Duration(milliseconds: 300), child: SizedBox( width: double.infinity, height: toolbarType == 'input' ? (_keyboardHeight > keyboardHeight ? _keyboardHeight : keyboardHeight) : emoteHeight, child: EmotePanel(onChoose: onChooseEmote), ), ), if (toolbarType == 'input' && keyboardHeight == 0.0) SizedBox( width: double.infinity, height: MediaQuery.of(context).padding.bottom, ) ], ), ); } }