diff --git a/android/build.gradle b/android/build.gradle index e1995b95d..784f8c778 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,6 +9,7 @@ allprojects { } google() mavenCentral() + maven { url 'https://jitpack.io' } } } diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index c19cfac41..c8d8dd738 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -41,41 +41,43 @@ class _EmotePanelState extends State return Column( children: [ Expanded( - child: TabBarView( - controller: _emotePanelController.tabController, - children: emotePackage.map( - (e) { - int size = e.emote!.first.meta!.size!; - int type = e.type!; - return Padding( - padding: const EdgeInsets.fromLTRB(12, 6, 12, 0), - child: type != 4 - ? GridView.builder( - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: (size == 1 ? 40 : 60), - crossAxisSpacing: 8, - mainAxisSpacing: 8, - mainAxisExtent: size == 1 ? 40 : 60, - ), - itemCount: e.emote!.length, - itemBuilder: (context, index) { - return Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(8), - 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: TabBarView( + controller: _emotePanelController.tabController, + children: emotePackage.map( + (e) { + int size = e.emote!.first.meta!.size!; + int type = e.type!; + return Padding( + padding: const EdgeInsets.fromLTRB(12, 6, 12, 0), + child: GridView.builder( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: + type == 4 ? 100 : (size == 1 ? 40 : 60), + crossAxisSpacing: 8, + mainAxisSpacing: 8, + mainAxisExtent: size == 1 ? 40 : 60, + ), + itemCount: e.emote!.length, + itemBuilder: (context, index) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + widget.onChoose(e, e.emote![index]); + }, + child: Padding( + padding: const EdgeInsets.all(6), + 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, @@ -83,42 +85,16 @@ class _EmotePanelState extends State e.emote![index].text!, type: 'emote', ), - ), - ), - ), - ); - }, - ) - : 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(), - )), + ); + }, + ), + ); + }, + ).toList(), + ), + ), Divider( height: 1, color: Theme.of(context).dividerColor.withOpacity(0.1), diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index d6ae92c4e..71f016077 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -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 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 @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 } 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 child: Stack( children: [ CustomScrollView( - controller: scrollController, + controller: _videoReplyController.scrollController, physics: const AlwaysScrollableScrollPhysics(), key: const PageStorageKey('评论'), slivers: [ @@ -243,28 +243,68 @@ class _VideoReplyPanelState extends State heroTag: null, onPressed: () { feedBack(); - 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, + Navigator.of(context) + .push( + GetDialogRoute( + pageBuilder: + (buildContext, animation, secondaryAnimation) { + return ReplyPage( + oid: _videoReplyController.aid ?? + IdUtils.bv2av(Get.parameters['bvid']!), + root: 0, + parent: 0, + replyType: ReplyType.video, + ); + }, + 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) + { + _videoReplyController.replyList + .insert(0, value['data']) + } + }, ); - }, - ).then( - (value) => { - // 完成评论,数据添加 - if (value != null && value['data'] != null) - { - _videoReplyController.replyList - .insert(0, value['data']) - } - }, - ); + // 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), diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 6e4e4122d..d7f1eac9f 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -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,28 +303,68 @@ class ReplyItem extends StatelessWidget { child: TextButton( onPressed: () { feedBack(); - 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']), - } - }); + Navigator.of(context) + .push( + GetDialogRoute( + pageBuilder: + (buildContext, animation, secondaryAnimation) { + return ReplyPage( + oid: replyItem!.oid, + root: replyItem!.rpid, + parent: replyItem!.rpid, + replyType: replyType, + replyItem: replyItem, + ); + }, + 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 && + addReply != null) + { + addReply?.call(value['data']) + // 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, diff --git a/lib/pages/video/detail/reply_new/reply_page.dart b/lib/pages/video/detail/reply_new/reply_page.dart new file mode 100644 index 000000000..f4ceeadbe --- /dev/null +++ b/lib/pages/video/detail/reply_new/reply_page.dart @@ -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 createState() => _ReplyPageState(); +} + +class _ReplyPageState extends State + with SingleTickerProviderStateMixin { + late final _focusNode = FocusNode(); + late final _controller = ChatBottomPanelContainerController(); + final TextEditingController _replyContentController = TextEditingController(); + PanelType _currentPanelType = PanelType.none; + bool _readOnly = false; + final _readOnlyStream = StreamController(); + late final _enableSend = StreamController(); + bool _enablePublish = false; + final _publishStream = StreamController(); + bool _selectKeyboard = true; + final _keyboardStream = StreamController.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( + 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), + ); + } +} diff --git a/lib/pages/video/detail/reply_new/toolbar_icon_button.dart b/lib/pages/video/detail/reply_new/toolbar_icon_button.dart index 047439b16..48e9e3d3d 100644 --- a/lib/pages/video/detail/reply_new/toolbar_icon_button.dart +++ b/lib/pages/video/detail/reply_new/toolbar_icon_button.dart @@ -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; diff --git a/lib/pages/video/detail/reply_new/view.dart b/lib/pages/video/detail/reply_new/view.dart index c299a9d76..ad6feddb4 100644 --- a/lib/pages/video/detail/reply_new/view.dart +++ b/lib/pages/video/detail/reply_new/view.dart @@ -214,7 +214,7 @@ class _VideoReplyNewDialogState extends State 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 FocusScope.of(context).unfocus(); }, icon: const Icon(Icons.emoji_emotions, size: 22), - toolbarType: toolbarType, + // toolbarType: toolbarType, selected: toolbarType == 'emote', ), const Spacer(), diff --git a/pubspec.lock b/pubspec.lock index ffdf374e0..6dc117c61 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index a675a1cdf..4009f16f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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