mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: new reply page
This commit is contained in:
@@ -9,6 +9,7 @@ allprojects {
|
||||
}
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,11 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
int type = e.type!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 12, 0),
|
||||
child: type != 4
|
||||
? GridView.builder(
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: (size == 1 ? 40 : 60),
|
||||
maxCrossAxisExtent:
|
||||
type == 4 ? 100 : (size == 1 ? 40 : 60),
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
mainAxisExtent: size == 1 ? 40 : 60,
|
||||
@@ -67,15 +67,17 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
onTap: () {
|
||||
widget.onChoose(e, e.emote![index]);
|
||||
},
|
||||
child: Tooltip(
|
||||
message: e.emote![index].text!
|
||||
.substring(
|
||||
1,
|
||||
e.emote![index].text!.length -
|
||||
1),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: NetworkImgLayer(
|
||||
child: type == 4
|
||||
? Center(
|
||||
child: Text(
|
||||
e.emote![index].text!,
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 1,
|
||||
),
|
||||
)
|
||||
: NetworkImgLayer(
|
||||
src: e.emote![index].url!,
|
||||
width: size * 38,
|
||||
height: size * 38,
|
||||
@@ -85,40 +87,14 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: e.emote!
|
||||
.map(
|
||||
(item) => Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.circular(8),
|
||||
onTap: () {
|
||||
widget.onChoose(e, item);
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(6),
|
||||
child: Text(item.text!),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -5,9 +6,9 @@ import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/skeleton/video_reply.dart';
|
||||
import 'package:PiliPalaX/models/common/reply_type.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_new/index.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/reply_item.dart';
|
||||
|
||||
@@ -35,7 +36,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||
late VideoReplyController _videoReplyController;
|
||||
late AnimationController fabAnimationCtr;
|
||||
late ScrollController scrollController;
|
||||
|
||||
bool _isFabVisible = true;
|
||||
String replyLevel = '1';
|
||||
@@ -70,17 +70,17 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
@override
|
||||
void dispose() {
|
||||
fabAnimationCtr.dispose();
|
||||
scrollController.removeListener(() {});
|
||||
scrollController.dispose();
|
||||
_videoReplyController.scrollController.removeListener(() {});
|
||||
_videoReplyController.scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void scrollListener() {
|
||||
scrollController = _videoReplyController.scrollController;
|
||||
scrollController.addListener(
|
||||
_videoReplyController.scrollController.addListener(
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 300) {
|
||||
if (_videoReplyController.scrollController.position.pixels >=
|
||||
_videoReplyController.scrollController.position.maxScrollExtent -
|
||||
300) {
|
||||
EasyThrottle.throttle('replylist', const Duration(milliseconds: 200),
|
||||
() {
|
||||
_videoReplyController.onLoad();
|
||||
@@ -88,7 +88,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
}
|
||||
|
||||
final ScrollDirection direction =
|
||||
scrollController.position.userScrollDirection;
|
||||
_videoReplyController.scrollController.position.userScrollDirection;
|
||||
if (direction == ScrollDirection.forward) {
|
||||
_showFab();
|
||||
} else if (direction == ScrollDirection.reverse) {
|
||||
@@ -134,7 +134,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: scrollController,
|
||||
controller: _videoReplyController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
key: const PageStorageKey<String>('评论'),
|
||||
slivers: <Widget>[
|
||||
@@ -243,11 +243,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext context) {
|
||||
return VideoReplyNewDialog(
|
||||
Navigator.of(context)
|
||||
.push(
|
||||
GetDialogRoute(
|
||||
pageBuilder:
|
||||
(buildContext, animation, secondaryAnimation) {
|
||||
return ReplyPage(
|
||||
oid: _videoReplyController.aid ??
|
||||
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||
root: 0,
|
||||
@@ -255,7 +256,24 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
replyType: ReplyType.video,
|
||||
);
|
||||
},
|
||||
).then(
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
transitionBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.linear;
|
||||
|
||||
var tween = Tween(begin: begin, end: end)
|
||||
.chain(CurveTween(curve: curve));
|
||||
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.then(
|
||||
(value) => {
|
||||
// 完成评论,数据添加
|
||||
if (value != null && value['data'] != null)
|
||||
@@ -265,6 +283,28 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
}
|
||||
},
|
||||
);
|
||||
// showModalBottomSheet(
|
||||
// context: context,
|
||||
// isScrollControlled: true,
|
||||
// builder: (BuildContext context) {
|
||||
// return VideoReplyNewDialog(
|
||||
// oid: _videoReplyController.aid ??
|
||||
// IdUtils.bv2av(Get.parameters['bvid']!),
|
||||
// root: 0,
|
||||
// parent: 0,
|
||||
// replyType: ReplyType.video,
|
||||
// );
|
||||
// },
|
||||
// ).then(
|
||||
// (value) => {
|
||||
// // 完成评论,数据添加
|
||||
// if (value != null && value['data'] != null)
|
||||
// {
|
||||
// _videoReplyController.replyList
|
||||
// .insert(0, value['data'])
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
},
|
||||
tooltip: '发表评论',
|
||||
child: const Icon(Icons.reply),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
@@ -11,12 +12,12 @@ import 'package:PiliPalaX/models/common/reply_type.dart';
|
||||
import 'package:PiliPalaX/models/video/reply/item.dart';
|
||||
import 'package:PiliPalaX/pages/preview/index.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_new/index.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/url_utils.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import '../../../../../utils/app_scheme.dart';
|
||||
import '../../reply_new/reply_page.dart';
|
||||
import 'zan.dart';
|
||||
|
||||
Box setting = GStorage.setting;
|
||||
@@ -302,11 +303,12 @@ class ReplyItem extends StatelessWidget {
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (builder) {
|
||||
return VideoReplyNewDialog(
|
||||
Navigator.of(context)
|
||||
.push(
|
||||
GetDialogRoute(
|
||||
pageBuilder:
|
||||
(buildContext, animation, secondaryAnimation) {
|
||||
return ReplyPage(
|
||||
oid: replyItem!.oid,
|
||||
root: replyItem!.rpid,
|
||||
parent: replyItem!.rpid,
|
||||
@@ -314,7 +316,24 @@ class ReplyItem extends StatelessWidget {
|
||||
replyItem: replyItem,
|
||||
);
|
||||
},
|
||||
).then((value) => {
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
transitionBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.linear;
|
||||
|
||||
var tween = Tween(begin: begin, end: end)
|
||||
.chain(CurveTween(curve: curve));
|
||||
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.then((value) => {
|
||||
// 完成评论,数据添加
|
||||
if (value != null &&
|
||||
value['data'] != null &&
|
||||
@@ -324,6 +343,28 @@ class ReplyItem extends StatelessWidget {
|
||||
// replyControl.replies.add(value['data']),
|
||||
}
|
||||
});
|
||||
// showModalBottomSheet(
|
||||
// context: context,
|
||||
// isScrollControlled: true,
|
||||
// builder: (builder) {
|
||||
// return VideoReplyNewDialog(
|
||||
// oid: replyItem!.oid,
|
||||
// root: replyItem!.rpid,
|
||||
// parent: replyItem!.rpid,
|
||||
// replyType: replyType,
|
||||
// replyItem: replyItem,
|
||||
// );
|
||||
// },
|
||||
// ).then((value) => {
|
||||
// // 完成评论,数据添加
|
||||
// if (value != null &&
|
||||
// value['data'] != null &&
|
||||
// addReply != null)
|
||||
// {
|
||||
// addReply?.call(value['data'])
|
||||
// // replyControl.replies.add(value['data']),
|
||||
// }
|
||||
// });
|
||||
},
|
||||
child: Row(children: [
|
||||
Icon(Icons.reply,
|
||||
|
||||
377
lib/pages/video/detail/reply_new/reply_page.dart
Normal file
377
lib/pages/video/detail/reply_new/reply_page.dart
Normal file
@@ -0,0 +1,377 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/widgets/no_splash_factory.dart';
|
||||
import 'package:PiliPalaX/http/video.dart';
|
||||
import 'package:PiliPalaX/models/common/reply_type.dart';
|
||||
import 'package:PiliPalaX/models/video/reply/emote.dart';
|
||||
import 'package:PiliPalaX/models/video/reply/item.dart';
|
||||
import 'package:PiliPalaX/pages/emote/index.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/pages/emote/view.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||
|
||||
enum PanelType { none, keyboard, emoji }
|
||||
|
||||
class ReplyPage extends StatefulWidget {
|
||||
final int? oid;
|
||||
final int? root;
|
||||
final int? parent;
|
||||
final ReplyType? replyType;
|
||||
final ReplyItemModel? replyItem;
|
||||
|
||||
const ReplyPage({
|
||||
super.key,
|
||||
this.oid,
|
||||
this.root,
|
||||
this.parent,
|
||||
this.replyType,
|
||||
this.replyItem,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReplyPage> createState() => _ReplyPageState();
|
||||
}
|
||||
|
||||
class _ReplyPageState extends State<ReplyPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final _focusNode = FocusNode();
|
||||
late final _controller = ChatBottomPanelContainerController<PanelType>();
|
||||
final TextEditingController _replyContentController = TextEditingController();
|
||||
PanelType _currentPanelType = PanelType.none;
|
||||
bool _readOnly = false;
|
||||
final _readOnlyStream = StreamController<bool>();
|
||||
late final _enableSend = StreamController<bool>();
|
||||
bool _enablePublish = false;
|
||||
final _publishStream = StreamController<bool>();
|
||||
bool _selectKeyboard = true;
|
||||
final _keyboardStream = StreamController<bool>.broadcast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (mounted) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
_publishStream.close();
|
||||
_readOnlyStream.close();
|
||||
_enableSend.close();
|
||||
_focusNode.dispose();
|
||||
_replyContentController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
context: context,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
splashFactory: NoSplashFactory(),
|
||||
onTap: Get.back,
|
||||
),
|
||||
),
|
||||
_buildInputView(),
|
||||
_buildPanelContainer(),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildPanelContainer() {
|
||||
return ChatBottomPanelContainer<PanelType>(
|
||||
controller: _controller,
|
||||
inputFocusNode: _focusNode,
|
||||
otherPanelWidget: (type) {
|
||||
if (type == null) return const SizedBox.shrink();
|
||||
switch (type) {
|
||||
case PanelType.emoji:
|
||||
return _buildEmojiPickerPanel();
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
onPanelTypeChange: (panelType, data) {
|
||||
debugPrint('panelType: $panelType');
|
||||
switch (panelType) {
|
||||
case ChatBottomPanelType.none:
|
||||
_currentPanelType = PanelType.none;
|
||||
break;
|
||||
case ChatBottomPanelType.keyboard:
|
||||
_currentPanelType = PanelType.keyboard;
|
||||
break;
|
||||
case ChatBottomPanelType.other:
|
||||
if (data == null) return;
|
||||
switch (data) {
|
||||
case PanelType.emoji:
|
||||
_currentPanelType = PanelType.emoji;
|
||||
break;
|
||||
default:
|
||||
_currentPanelType = PanelType.none;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
panelBgColor: Theme.of(context).colorScheme.surface,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmojiPickerPanel() {
|
||||
double height = 200;
|
||||
final keyboardHeight = _controller.keyboardHeight;
|
||||
if (keyboardHeight != 0) {
|
||||
height = max(height, keyboardHeight);
|
||||
}
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: EmotePanel(onChoose: onChooseEmote),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputView() {
|
||||
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: Form(
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Listener(
|
||||
onPointerUp: (event) {
|
||||
if (_readOnly) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
if (!_selectKeyboard) {
|
||||
_selectKeyboard = true;
|
||||
_keyboardStream.add(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: StreamBuilder(
|
||||
initialData: false,
|
||||
stream: _readOnlyStream.stream,
|
||||
builder: (context, snapshot) => TextField(
|
||||
controller: _replyContentController,
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
autofocus: false,
|
||||
readOnly: snapshot.data ?? false,
|
||||
onChanged: (value) {
|
||||
if (value.isNotEmpty && !_enablePublish) {
|
||||
_enablePublish = true;
|
||||
_publishStream.add(true);
|
||||
} else if (value.isEmpty && _enablePublish) {
|
||||
_enablePublish = false;
|
||||
_publishStream.add(false);
|
||||
}
|
||||
},
|
||||
focusNode: _focusNode,
|
||||
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: [
|
||||
StreamBuilder(
|
||||
initialData: true,
|
||||
stream: _keyboardStream.stream,
|
||||
builder: (_, snapshot) => ToolbarIconButton(
|
||||
tooltip: '输入',
|
||||
onPressed: () {
|
||||
if (!_selectKeyboard) {
|
||||
_selectKeyboard = true;
|
||||
_keyboardStream.add(true);
|
||||
}
|
||||
updatePanelType(PanelType.keyboard);
|
||||
},
|
||||
icon: const Icon(Icons.keyboard, size: 22),
|
||||
selected: snapshot.data!,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
StreamBuilder(
|
||||
initialData: true,
|
||||
stream: _keyboardStream.stream,
|
||||
builder: (_, snapshot) => ToolbarIconButton(
|
||||
tooltip: '表情',
|
||||
onPressed: () {
|
||||
updatePanelType(
|
||||
PanelType.emoji == _currentPanelType
|
||||
? PanelType.keyboard
|
||||
: PanelType.emoji,
|
||||
);
|
||||
if (_selectKeyboard) {
|
||||
_selectKeyboard = false;
|
||||
_keyboardStream.add(false);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.emoji_emotions, size: 22),
|
||||
selected: !snapshot.data!,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
initialData: false,
|
||||
stream: _publishStream.stream,
|
||||
builder: (_, 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('发送'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
updatePanelType(PanelType type) async {
|
||||
final isSwitchToKeyboard = PanelType.keyboard == type;
|
||||
final isSwitchToEmojiPanel = PanelType.emoji == type;
|
||||
bool isUpdated = false;
|
||||
switch (type) {
|
||||
case PanelType.keyboard:
|
||||
updateInputView(isReadOnly: false);
|
||||
break;
|
||||
case PanelType.emoji:
|
||||
isUpdated = updateInputView(isReadOnly: true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
updatePanelTypeFunc() {
|
||||
_controller.updatePanelType(
|
||||
isSwitchToKeyboard
|
||||
? ChatBottomPanelType.keyboard
|
||||
: ChatBottomPanelType.other,
|
||||
data: type,
|
||||
forceHandleFocus: isSwitchToEmojiPanel
|
||||
? ChatBottomHandleFocus.requestFocus
|
||||
: ChatBottomHandleFocus.none,
|
||||
);
|
||||
}
|
||||
|
||||
if (isUpdated) {
|
||||
// Waiting for the input view to update.
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
updatePanelTypeFunc();
|
||||
});
|
||||
} else {
|
||||
updatePanelTypeFunc();
|
||||
}
|
||||
}
|
||||
|
||||
hidePanel() async {
|
||||
if (_focusNode.hasFocus) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
_focusNode.unfocus();
|
||||
}
|
||||
updateInputView(isReadOnly: false);
|
||||
if (ChatBottomPanelType.none == _controller.currentPanelType) return;
|
||||
_controller.updatePanelType(ChatBottomPanelType.none);
|
||||
}
|
||||
|
||||
bool updateInputView({
|
||||
required bool isReadOnly,
|
||||
}) {
|
||||
if (_readOnly != isReadOnly) {
|
||||
_readOnly = isReadOnly;
|
||||
_readOnlyStream.add(_readOnly);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
class ToolbarIconButton extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
final Icon icon;
|
||||
final String toolbarType;
|
||||
final bool selected;
|
||||
final String tooltip;
|
||||
|
||||
@@ -11,7 +10,6 @@ class ToolbarIconButton extends StatelessWidget {
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
required this.toolbarType,
|
||||
required this.selected,
|
||||
required this.tooltip,
|
||||
});
|
||||
@@ -30,8 +28,8 @@ class ToolbarIconButton extends StatelessWidget {
|
||||
? Theme.of(context).colorScheme.onSecondaryContainer
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
return selected
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: null;
|
||||
|
||||
@@ -214,7 +214,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
||||
},
|
||||
icon: const Icon(Icons.keyboard, size: 22),
|
||||
toolbarType: toolbarType,
|
||||
// toolbarType: toolbarType,
|
||||
selected: toolbarType == 'input',
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
@@ -229,7 +229,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
icon: const Icon(Icons.emoji_emotions, size: 22),
|
||||
toolbarType: toolbarType,
|
||||
// toolbarType: toolbarType,
|
||||
selected: toolbarType == 'emote',
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
64
pubspec.lock
64
pubspec.lock
@@ -255,6 +255,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
chat_bottom_container:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: chat_bottom_container
|
||||
sha256: fc2e690892699604524bf4a4c7ba9b3281ae060c90154b55b38116ce7812c3d8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1421,6 +1429,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -148,6 +148,7 @@ dependencies:
|
||||
marquee: ^2.2.3
|
||||
#富文本
|
||||
extended_text: ^14.0.1
|
||||
chat_bottom_container: ^0.2.0
|
||||
dependency_overrides:
|
||||
mime:
|
||||
git: https://github.com/orz12/mime.git
|
||||
|
||||
Reference in New Issue
Block a user