mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: send live emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
library video_reply_new;
|
||||
|
||||
export './view.dart';
|
||||
@@ -1,282 +0,0 @@
|
||||
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';
|
||||
|
||||
@Deprecated('Use ReplyPage instead')
|
||||
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<VideoReplyNewDialog> createState() => _VideoReplyNewDialogState();
|
||||
}
|
||||
|
||||
class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
with WidgetsBindingObserver {
|
||||
final TextEditingController _replyContentController = TextEditingController();
|
||||
final FocusNode replyContentFocusNode = FocusNode();
|
||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
||||
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<bool>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 监听输入框聚焦
|
||||
// replyContentFocusNode.addListener(_onFocus);
|
||||
// 界面观察者 必须
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// 自动聚焦
|
||||
_autoFocus();
|
||||
// 监听聚焦状态
|
||||
_focusListener();
|
||||
}
|
||||
|
||||
_autoFocus() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (mounted) {
|
||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
||||
}
|
||||
}
|
||||
|
||||
_focusListener() {
|
||||
replyContentFocusNode.addListener(listener);
|
||||
}
|
||||
|
||||
void listener() {
|
||||
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(listener);
|
||||
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.distinct(),
|
||||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,6 @@ class SendDanmakuPanel extends CommonPublishPage {
|
||||
final dynamic bvid;
|
||||
final dynamic progress;
|
||||
|
||||
// live
|
||||
final dynamic roomId;
|
||||
|
||||
final ValueChanged<DanmakuContentItem> callback;
|
||||
final bool darkVideoPage;
|
||||
|
||||
@@ -36,7 +33,6 @@ class SendDanmakuPanel extends CommonPublishPage {
|
||||
this.cid,
|
||||
this.bvid,
|
||||
this.progress,
|
||||
this.roomId,
|
||||
required this.callback,
|
||||
required this.darkVideoPage,
|
||||
this.dmConfig,
|
||||
@@ -86,18 +82,18 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
||||
|
||||
get _buildColorPanel => Expanded(
|
||||
child: Obx(
|
||||
() => LayoutBuilder(
|
||||
() => Builder(
|
||||
key: ValueKey(_color.value),
|
||||
builder: (context, constraints) {
|
||||
final int crossAxisCount = (constraints.maxWidth / 40).toInt();
|
||||
builder: (context) {
|
||||
final bool isCustomColor = _colorList.contains(_color.value).not;
|
||||
final int length =
|
||||
_colorList.length + (isCustomColor ? 1 : 0) + 1;
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 40,
|
||||
mainAxisExtent: 40,
|
||||
crossAxisSpacing: 4,
|
||||
mainAxisSpacing: 4,
|
||||
),
|
||||
@@ -341,28 +337,27 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.roomId == null)
|
||||
Obx(
|
||||
() => iconButton(
|
||||
context: context,
|
||||
tooltip: '弹幕样式',
|
||||
onPressed: () {
|
||||
if (selectKeyboard.value) {
|
||||
selectKeyboard.value = false;
|
||||
updatePanelType(PanelType.emoji);
|
||||
} else {
|
||||
selectKeyboard.value = true;
|
||||
updatePanelType(PanelType.keyboard);
|
||||
}
|
||||
},
|
||||
bgColor: Colors.transparent,
|
||||
iconSize: 24,
|
||||
icon: Icons.text_format,
|
||||
iconColor: selectKeyboard.value.not
|
||||
? themeData.colorScheme.primary
|
||||
: themeData.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Obx(
|
||||
() => iconButton(
|
||||
context: context,
|
||||
tooltip: '弹幕样式',
|
||||
onPressed: () {
|
||||
if (selectKeyboard.value) {
|
||||
selectKeyboard.value = false;
|
||||
updatePanelType(PanelType.emoji);
|
||||
} else {
|
||||
selectKeyboard.value = true;
|
||||
updatePanelType(PanelType.keyboard);
|
||||
}
|
||||
},
|
||||
bgColor: Colors.transparent,
|
||||
iconSize: 24,
|
||||
icon: Icons.text_format,
|
||||
iconColor: selectKeyboard.value.not
|
||||
? themeData.colorScheme.primary
|
||||
: themeData.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Form(
|
||||
@@ -470,20 +465,15 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
||||
@override
|
||||
Future onCustomPublish({required String message, List? pictures}) async {
|
||||
SmartDialog.showLoading(msg: '发送中...');
|
||||
final res = widget.roomId != null
|
||||
? await LiveHttp.sendLiveMsg(
|
||||
roomId: widget.roomId,
|
||||
msg: editController.text,
|
||||
)
|
||||
: await DanmakuHttp.shootDanmaku(
|
||||
oid: widget.cid,
|
||||
bvid: widget.bvid,
|
||||
progress: widget.progress,
|
||||
msg: editController.text,
|
||||
mode: _mode.value,
|
||||
fontsize: _fontsize.value,
|
||||
color: _color.value.value & 0xFFFFFF,
|
||||
);
|
||||
final res = await DanmakuHttp.shootDanmaku(
|
||||
oid: widget.cid,
|
||||
bvid: widget.bvid,
|
||||
progress: widget.progress,
|
||||
msg: editController.text,
|
||||
mode: _mode.value,
|
||||
fontsize: _fontsize.value,
|
||||
color: _color.value.value & 0xFFFFFF,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
if (res['status']) {
|
||||
Get.back();
|
||||
|
||||
Reference in New Issue
Block a user