diff --git a/lib/http/api.dart b/lib/http/api.dart index b19088f5..58c18d88 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -757,4 +757,7 @@ class Api { static const String replyTop = '/x/v2/reply/top'; static const String getCoin = '${HttpString.accountBaseUrl}/site/getCoin'; + + static const String getLiveEmoticons = + '${HttpString.liveBaseUrl}/xlive/web-ucenter/v2/emoticon/GetEmoticons'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index 8b2a1c2f..b5f4c049 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -2,6 +2,8 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/live/danmu_info.dart'; import 'package:PiliPlus/models/live/follow.dart'; +import 'package:PiliPlus/models/live/live_emoticons/data.dart'; +import 'package:PiliPlus/models/live/live_emoticons/datum.dart'; import 'package:dio/dio.dart'; import '../models/live/item.dart'; import '../models/live/room_info.dart'; @@ -24,35 +26,34 @@ class LiveHttp { } } - static Future sendLiveMsg({ - roomId, - msg, - }) async { + static Future sendLiveMsg({roomId, msg, dmType, emoticonOptions}) async { dynamic csrf = await Request.getCsrf(); var res = await Request().post( Api.sendLiveMsg, - data: { + data: FormData.fromMap({ 'bubble': 0, 'msg': msg, 'color': 16777215, 'mode': 1, - 'room_type': 0, - 'jumpfrom': 71000, - 'reply_mid': 0, - 'reply_attr': 0, - 'replay_dmid': '', - 'statistics': Constants.statistics, - 'reply_type': 0, - 'reply_uname': '', + if (dmType != null) 'dm_type': dmType, + if (emoticonOptions != null) + 'emoticonOptions': emoticonOptions + else ...{ + 'room_type': 0, + 'jumpfrom': 0, + 'reply_mid': 0, + 'reply_attr': 0, + 'replay_dmid': '', + 'statistics': Constants.statistics, + 'reply_type': 0, + 'reply_uname': '', + }, 'fontsize': 25, 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'roomid': roomId, 'csrf': csrf, 'csrf_token': csrf, - }, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), + }), ); if (res.data['code'] == 0) { return { @@ -146,4 +147,21 @@ class LiveHttp { }; } } + + static Future?>> getLiveEmoticons( + {required int roomId}) async { + var res = await Request().get( + Api.getLiveEmoticons, + queryParameters: { + 'platform': 'pc', + 'room_id': roomId, + }, + ); + if (res.data['code'] == 0) { + return LoadingState.success( + LiveEmoteData.fromJson(res.data['data']).data); + } else { + return LoadingState.error(res.data['message']); + } + } } diff --git a/lib/models/live/live_emoticons/data.dart b/lib/models/live/live_emoticons/data.dart new file mode 100644 index 00000000..644b432a --- /dev/null +++ b/lib/models/live/live_emoticons/data.dart @@ -0,0 +1,23 @@ +import 'datum.dart'; + +class LiveEmoteData { + int? fansBrand; + List? data; + dynamic purchaseUrl; + + LiveEmoteData({this.fansBrand, this.data, this.purchaseUrl}); + + factory LiveEmoteData.fromJson(Map json) => LiveEmoteData( + fansBrand: json['fans_brand'] as int?, + data: (json['data'] as List?) + ?.map((e) => LiveEmoteDatum.fromJson(e as Map)) + .toList(), + purchaseUrl: json['purchase_url'] as dynamic, + ); + + Map toJson() => { + 'fans_brand': fansBrand, + 'data': data?.map((e) => e.toJson()).toList(), + 'purchase_url': purchaseUrl, + }; +} diff --git a/lib/models/live/live_emoticons/datum.dart b/lib/models/live/live_emoticons/datum.dart new file mode 100644 index 00000000..2215701e --- /dev/null +++ b/lib/models/live/live_emoticons/datum.dart @@ -0,0 +1,71 @@ +import 'emoticon.dart'; +import 'top_show.dart'; +import 'top_show_recent.dart'; + +class LiveEmoteDatum { + List? emoticons; + int? pkgId; + String? pkgName; + int? pkgType; + String? pkgDescript; + int? pkgPerm; + int? unlockIdentity; + int? unlockNeedGift; + String? currentCover; + List? recentlyUsedEmoticons; + TopShow? topShow; + TopShowRecent? topShowRecent; + + LiveEmoteDatum({ + this.emoticons, + this.pkgId, + this.pkgName, + this.pkgType, + this.pkgDescript, + this.pkgPerm, + this.unlockIdentity, + this.unlockNeedGift, + this.currentCover, + this.recentlyUsedEmoticons, + this.topShow, + this.topShowRecent, + }); + + factory LiveEmoteDatum.fromJson(Map json) => LiveEmoteDatum( + emoticons: (json['emoticons'] as List?) + ?.map((e) => LiveEmoticon.fromJson(e as Map)) + .toList(), + pkgId: json['pkg_id'] as int?, + pkgName: json['pkg_name'] as String?, + pkgType: json['pkg_type'] as int?, + pkgDescript: json['pkg_descript'] as String?, + pkgPerm: json['pkg_perm'] as int?, + unlockIdentity: json['unlock_identity'] as int?, + unlockNeedGift: json['unlock_need_gift'] as int?, + currentCover: json['current_cover'] as String?, + recentlyUsedEmoticons: + json['recently_used_emoticons'] as List?, + topShow: json['top_show'] == null + ? null + : TopShow.fromJson(json['top_show'] as Map), + topShowRecent: json['top_show_recent'] == null + ? null + : TopShowRecent.fromJson( + json['top_show_recent'] as Map), + ); + + Map toJson() => { + 'emoticons': emoticons?.map((e) => e.toJson()).toList(), + 'pkg_id': pkgId, + 'pkg_name': pkgName, + 'pkg_type': pkgType, + 'pkg_descript': pkgDescript, + 'pkg_perm': pkgPerm, + 'unlock_identity': unlockIdentity, + 'unlock_need_gift': unlockNeedGift, + 'current_cover': currentCover, + 'recently_used_emoticons': recentlyUsedEmoticons, + 'top_show': topShow?.toJson(), + 'top_show_recent': topShowRecent?.toJson(), + }; +} diff --git a/lib/models/live/live_emoticons/emoticon.dart b/lib/models/live/live_emoticons/emoticon.dart new file mode 100644 index 00000000..3bce8b81 --- /dev/null +++ b/lib/models/live/live_emoticons/emoticon.dart @@ -0,0 +1,83 @@ +class LiveEmoticon { + String? emoji; + String? descript; + String? url; + int? isDynamic; + int? inPlayerArea; + int? width; + int? height; + int? identity; + int? unlockNeedGift; + int? perm; + int? unlockNeedLevel; + int? emoticonValueType; + int? bulgeDisplay; + String? unlockShowText; + String? unlockShowColor; + String? emoticonUnique; + String? unlockShowImage; + int? emoticonId; + + LiveEmoticon({ + this.emoji, + this.descript, + this.url, + this.isDynamic, + this.inPlayerArea, + this.width, + this.height, + this.identity, + this.unlockNeedGift, + this.perm, + this.unlockNeedLevel, + this.emoticonValueType, + this.bulgeDisplay, + this.unlockShowText, + this.unlockShowColor, + this.emoticonUnique, + this.unlockShowImage, + this.emoticonId, + }); + + factory LiveEmoticon.fromJson(Map json) => LiveEmoticon( + emoji: json['emoji'] as String?, + descript: json['descript'] as String?, + url: json['url'] as String?, + isDynamic: json['is_dynamic'] as int?, + inPlayerArea: json['in_player_area'] as int?, + width: json['width'] as int? ?? 0, + height: json['height'] as int? ?? 0, + identity: json['identity'] as int?, + unlockNeedGift: json['unlock_need_gift'] as int?, + perm: json['perm'] as int?, + unlockNeedLevel: json['unlock_need_level'] as int?, + emoticonValueType: json['emoticon_value_type'] as int?, + bulgeDisplay: json['bulge_display'] as int?, + unlockShowText: json['unlock_show_text'] as String?, + unlockShowColor: json['unlock_show_color'] as String?, + emoticonUnique: json['emoticon_unique'] as String?, + unlockShowImage: json['unlock_show_image'] as String?, + emoticonId: json['emoticon_id'] as int?, + ); + + Map toJson() => { + 'emoji': emoji, + 'descript': descript, + 'url': url, + 'is_dynamic': isDynamic, + 'in_player_area': inPlayerArea, + 'width': width, + 'height': height, + 'identity': identity, + 'unlock_need_gift': unlockNeedGift, + 'perm': perm, + 'unlock_need_level': unlockNeedLevel, + 'emoticon_value_type': emoticonValueType, + 'bulge_display': bulgeDisplay, + 'unlock_show_text': unlockShowText, + 'unlock_show_color': unlockShowColor, + 'emoticon_unique': emoticonUnique, + 'unlock_show_image': unlockShowImage, + 'emoticon_id': emoticonId, + }; +} diff --git a/lib/models/live/live_emoticons/live_emoticons.dart b/lib/models/live/live_emoticons/live_emoticons.dart new file mode 100644 index 00000000..15f2d22d --- /dev/null +++ b/lib/models/live/live_emoticons/live_emoticons.dart @@ -0,0 +1,26 @@ +import 'data.dart'; + +class LiveEmoticons { + int? code; + String? message; + int? ttl; + LiveEmoteData? data; + + LiveEmoticons({this.code, this.message, this.ttl, this.data}); + + factory LiveEmoticons.fromJson(Map json) => LiveEmoticons( + code: json['code'] as int?, + message: json['message'] as String?, + ttl: json['ttl'] as int?, + data: json['data'] == null + ? null + : LiveEmoteData.fromJson(json['data'] as Map), + ); + + Map toJson() => { + 'code': code, + 'message': message, + 'ttl': ttl, + 'data': data?.toJson(), + }; +} diff --git a/lib/models/live/live_emoticons/top_left.dart b/lib/models/live/live_emoticons/top_left.dart new file mode 100644 index 00000000..60b54478 --- /dev/null +++ b/lib/models/live/live_emoticons/top_left.dart @@ -0,0 +1,16 @@ +class TopLeft { + String? image; + String? text; + + TopLeft({this.image, this.text}); + + factory TopLeft.fromJson(Map json) => TopLeft( + image: json['image'] as String?, + text: json['text'] as String?, + ); + + Map toJson() => { + 'image': image, + 'text': text, + }; +} diff --git a/lib/models/live/live_emoticons/top_right.dart b/lib/models/live/live_emoticons/top_right.dart new file mode 100644 index 00000000..0c2249af --- /dev/null +++ b/lib/models/live/live_emoticons/top_right.dart @@ -0,0 +1,16 @@ +class TopRight { + String? image; + String? text; + + TopRight({this.image, this.text}); + + factory TopRight.fromJson(Map json) => TopRight( + image: json['image'] as String?, + text: json['text'] as String?, + ); + + Map toJson() => { + 'image': image, + 'text': text, + }; +} diff --git a/lib/models/live/live_emoticons/top_show.dart b/lib/models/live/live_emoticons/top_show.dart new file mode 100644 index 00000000..ce4700a7 --- /dev/null +++ b/lib/models/live/live_emoticons/top_show.dart @@ -0,0 +1,23 @@ +import 'top_left.dart'; +import 'top_right.dart'; + +class TopShow { + TopLeft? topLeft; + TopRight? topRight; + + TopShow({this.topLeft, this.topRight}); + + factory TopShow.fromJson(Map json) => TopShow( + topLeft: json['top_left'] == null + ? null + : TopLeft.fromJson(json['top_left'] as Map), + topRight: json['top_right'] == null + ? null + : TopRight.fromJson(json['top_right'] as Map), + ); + + Map toJson() => { + 'top_left': topLeft?.toJson(), + 'top_right': topRight?.toJson(), + }; +} diff --git a/lib/models/live/live_emoticons/top_show_recent.dart b/lib/models/live/live_emoticons/top_show_recent.dart new file mode 100644 index 00000000..9b3b9a06 --- /dev/null +++ b/lib/models/live/live_emoticons/top_show_recent.dart @@ -0,0 +1,23 @@ +import 'top_left.dart'; +import 'top_right.dart'; + +class TopShowRecent { + TopLeft? topLeft; + TopRight? topRight; + + TopShowRecent({this.topLeft, this.topRight}); + + factory TopShowRecent.fromJson(Map json) => TopShowRecent( + topLeft: json['top_left'] == null + ? null + : TopLeft.fromJson(json['top_left'] as Map), + topRight: json['top_right'] == null + ? null + : TopRight.fromJson(json['top_right'] as Map), + ); + + Map toJson() => { + 'top_left': topLeft?.toJson(), + 'top_right': topRight?.toJson(), + }; +} diff --git a/lib/pages/common/common_publish_page.dart b/lib/pages/common/common_publish_page.dart index e60b3a43..989af2ef 100644 --- a/lib/pages/common/common_publish_page.dart +++ b/lib/pages/common/common_publish_page.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart' show SourceModel, SourceType; import 'package:PiliPlus/http/msg.dart'; +import 'package:PiliPlus/models/live/live_emoticons/emoticon.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:chat_bottom_container/chat_bottom_container.dart'; import 'package:dio/dio.dart'; @@ -197,18 +198,29 @@ abstract class CommonPublishPageState Future onCustomPublish({required String message, List? pictures}); - void onChooseEmote(Packages package, Emote emote) { + void onChooseEmote(emote) { enablePublish.value = true; final int cursorPosition = editController.selection.baseOffset; final String currentText = editController.text; - final String newText = currentText.substring(0, cursorPosition) + - emote.text! + - currentText.substring(cursorPosition); - editController.value = TextEditingValue( - text: newText, - selection: - TextSelection.collapsed(offset: cursorPosition + emote.text!.length), - ); + if (emote is Emote) { + final String newText = currentText.substring(0, cursorPosition) + + emote.text! + + currentText.substring(cursorPosition); + editController.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed( + offset: cursorPosition + emote.text!.length), + ); + } else if (emote is LiveEmoticon) { + final String newText = currentText.substring(0, cursorPosition) + + emote.emoji! + + currentText.substring(cursorPosition); + editController.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed( + offset: cursorPosition + emote.emoji!.length), + ); + } widget.onSave?.call(editController.text); } diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index da065866..3dfb2ee6 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -8,7 +8,7 @@ import 'controller.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; class EmotePanel extends StatefulWidget { - final Function onChoose; + final ValueChanged onChoose; const EmotePanel({super.key, required this.onChoose}); @override @@ -60,7 +60,7 @@ class _EmotePanelState extends State child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () { - widget.onChoose(e, e.emote![index]); + widget.onChoose(e.emote![index]); }, child: Padding( padding: const EdgeInsets.all(6), diff --git a/lib/pages/live_emote/controller.dart b/lib/pages/live_emote/controller.dart new file mode 100644 index 00000000..6a74b62f --- /dev/null +++ b/lib/pages/live_emote/controller.dart @@ -0,0 +1,41 @@ +import 'package:PiliPlus/http/live.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/live/live_emoticons/datum.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class LiveEmotePanelController + extends CommonListController?, LiveEmoteDatum> + with GetTickerProviderStateMixin { + LiveEmotePanelController(this.roomId); + final int roomId; + TabController? tabController; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + bool customHandleResponse( + bool isRefresh, Success?> response) { + if (response.response?.isNotEmpty == true) { + tabController = + TabController(length: response.response!.length, vsync: this); + } + loadingState.value = response; + return true; + } + + @override + Future?>> customGetData() => + LiveHttp.getLiveEmoticons(roomId: roomId); + + @override + void onClose() { + tabController?.dispose(); + super.onClose(); + } +} diff --git a/lib/pages/live_emote/index.dart b/lib/pages/live_emote/index.dart new file mode 100644 index 00000000..4bd3ddbb --- /dev/null +++ b/lib/pages/live_emote/index.dart @@ -0,0 +1,4 @@ +library emote; + +export 'controller.dart'; +export 'view.dart'; diff --git a/lib/pages/live_emote/view.dart b/lib/pages/live_emote/view.dart new file mode 100644 index 00000000..bfcb44e9 --- /dev/null +++ b/lib/pages/live_emote/view.dart @@ -0,0 +1,148 @@ +import 'dart:math'; + +import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/live/live_emoticons/datum.dart'; +import 'package:PiliPlus/models/live/live_emoticons/emoticon.dart'; +import 'package:PiliPlus/utils/extension.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../common/widgets/network_img_layer.dart'; +import 'controller.dart'; +import 'package:PiliPlus/common/widgets/scroll_physics.dart'; + +class LiveEmotePanel extends StatefulWidget { + final int roomId; + final ValueChanged onChoose; + final ValueChanged onSendEmoticonUnique; + const LiveEmotePanel({ + super.key, + required this.roomId, + required this.onChoose, + required this.onSendEmoticonUnique, + }); + + @override + State createState() => _LiveEmotePanelState(); +} + +class _LiveEmotePanelState extends State + with AutomaticKeepAliveClientMixin { + late final LiveEmotePanelController _emotePanelController = Get.put( + LiveEmotePanelController(widget.roomId), + tag: widget.roomId.toString(), + ); + + @override + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + super.build(context); + return Obx(() => _buildBody(_emotePanelController.loadingState.value)); + } + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => loadingWidget, + Success() => loadingState.response?.isNotEmpty == true + ? Column( + children: [ + Expanded( + child: tabBarView( + controller: _emotePanelController.tabController, + children: loadingState.response!.map( + (item) { + if (item.emoticons.isNullOrEmpty) { + return const SizedBox.shrink(); + } + double widthFac = + max(1, item.emoticons!.first.width! / 80); + double heightFac = + max(1, item.emoticons!.first.height! / 80); + return Padding( + padding: const EdgeInsets.fromLTRB(12, 6, 12, 0), + child: GridView.builder( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: widthFac * 40, + mainAxisExtent: heightFac * 40, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: item.emoticons!.length, + itemBuilder: (context, index) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + if (item.pkgType == 3) { + widget.onChoose(item.emoticons![index]); + } else { + widget.onSendEmoticonUnique( + item.emoticons![index], + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(6), + child: NetworkImgLayer( + src: item.emoticons![index].url!, + width: widthFac * 38, + height: heightFac * 38, + type: 'emote', + quality: item.pkgType == 3 ? null : 80, + ), + ), + ), + ); + }, + ), + ); + }, + ).toList(), + ), + ), + Divider( + height: 1, + color: Theme.of(context).dividerColor.withOpacity(0.1), + ), + TabBar( + controller: _emotePanelController.tabController, + padding: const EdgeInsets.only(right: 60), + dividerColor: Colors.transparent, + dividerHeight: 0, + isScrollable: true, + tabs: loadingState.response! + .map( + (item) => Padding( + padding: const EdgeInsets.all(8), + child: NetworkImgLayer( + width: 24, + height: 24, + type: 'emote', + src: item.currentCover, + ), + ), + ) + .toList(), + ), + SizedBox(height: MediaQuery.of(context).padding.bottom), + ], + ) + : _errorWidget, + Error() => _errorWidget, + LoadingState() => throw UnimplementedError(), + }; + } + + Widget get _errorWidget => Center( + child: IconButton( + onPressed: () { + _emotePanelController.onReload(); + }, + icon: Icon(Icons.refresh), + ), + ); +} diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 174c136e..3882db71 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -4,7 +4,6 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/live/danmu_info.dart'; import 'package:PiliPlus/models/live/quality.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; -import 'package:PiliPlus/pages/video/detail/widgets/send_danmaku_panel.dart'; import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/tcp/live.dart'; import 'package:PiliPlus/utils/danmaku.dart'; @@ -13,13 +12,11 @@ import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/models/live/room_info.dart'; import 'package:PiliPlus/plugin/pl_player/index.dart'; -import 'package:get/get_navigation/src/dialog/dialog_route.dart'; import '../../models/live/room_info_h5.dart'; import '../../utils/video_utils.dart'; @@ -248,41 +245,4 @@ class LiveRoomController extends GetxController { .description; await queryLiveInfo(); } - - void onSendDanmaku() { - if (!isLogin) { - SmartDialog.showToast('未登录'); - return; - } - Navigator.of(Get.context!).push( - GetDialogRoute( - pageBuilder: (buildContext, animation, secondaryAnimation) { - return SendDanmakuPanel( - roomId: roomId, - initialValue: savedDanmaku, - onSave: (danmaku) => savedDanmaku = danmaku, - callback: (danmakuModel) { - savedDanmaku = null; - plPlayerController.danmakuController?.addDanmaku(danmakuModel); - }, - darkVideoPage: false, - ); - }, - 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, - ); - }, - ), - ); - } } diff --git a/lib/pages/live_room/send_dm_panel.dart b/lib/pages/live_room/send_dm_panel.dart new file mode 100644 index 00000000..7703df0e --- /dev/null +++ b/lib/pages/live_room/send_dm_panel.dart @@ -0,0 +1,247 @@ +import 'dart:async'; + +import 'package:PiliPlus/http/live.dart'; +import 'package:PiliPlus/pages/common/common_publish_page.dart'; +import 'package:PiliPlus/pages/live_emote/controller.dart'; +import 'package:PiliPlus/pages/live_emote/view.dart'; +import 'package:PiliPlus/pages/live_room/controller.dart'; +import 'package:PiliPlus/pages/video/detail/reply_new/toolbar_icon_button.dart'; +import 'package:canvas_danmaku/models/danmaku_content_item.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart' hide MultipartFile; + +class LiveSendDmPanel extends CommonPublishPage { + final bool fromEmote; + final LiveRoomController liveRoomController; + + const LiveSendDmPanel({ + super.key, + super.initialValue, + super.onSave, + this.fromEmote = false, + required this.liveRoomController, + }); + + @override + State createState() => _ReplyPageState(); +} + +class _ReplyPageState extends CommonPublishPageState { + LiveRoomController get liveRoomController => widget.liveRoomController; + + @override + void initState() { + super.initState(); + if (widget.fromEmote) { + selectKeyboard.value = false; + updatePanelType(PanelType.emoji); + } + } + + @override + void dispose() { + Get.delete( + tag: liveRoomController.roomId.toString()); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MediaQuery.removePadding( + removeTop: true, + context: context, + child: GestureDetector( + onTap: Get.back, + child: LayoutBuilder( + builder: (context, constraints) { + bool isH = constraints.maxWidth > constraints.maxHeight; + late double padding = constraints.maxWidth * 0.12; + return Padding( + padding: EdgeInsets.symmetric(horizontal: isH ? padding : 0), + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: GestureDetector( + onTap: () {}, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + buildInputView(), + buildPanelContainer( + Theme.of(context).colorScheme.surface), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + @override + Widget? customPanel(double height) => SizedBox( + height: height, + child: LiveEmotePanel( + onChoose: onChooseEmote, + roomId: liveRoomController.roomId, + onSendEmoticonUnique: (emote) { + onCustomPublish( + message: emote.emoticonUnique!, + dmType: 1, + emoticonOptions: '[object Object]', + ); + }, + ), + ); + + Widget buildInputView() { + return Container( + clipBehavior: Clip.hardEdge, + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + 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.value) { + updatePanelType(PanelType.keyboard); + selectKeyboard.value = true; + } + }, + child: Obx( + () => TextField( + controller: editController, + minLines: 1, + maxLines: 2, + autofocus: false, + readOnly: readOnly.value, + onChanged: (value) { + bool isEmpty = value.trim().isEmpty; + if (!isEmpty && !enablePublish.value) { + enablePublish.value = true; + } else if (isEmpty && enablePublish.value) { + enablePublish.value = false; + } + liveRoomController.savedDanmaku = value; + }, + focusNode: focusNode, + decoration: InputDecoration( + hintText: "输入弹幕内容", + border: InputBorder.none, + hintStyle: TextStyle(fontSize: 14)), + style: Theme.of(context).textTheme.bodyLarge, + inputFormatters: [LengthLimitingTextInputFormatter(20)], + ), + ), + ), + ), + ), + 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: [ + Obx( + () => ToolbarIconButton( + tooltip: '输入', + onPressed: () { + if (!selectKeyboard.value) { + selectKeyboard.value = true; + updatePanelType(PanelType.keyboard); + } + }, + icon: const Icon(Icons.keyboard, size: 22), + selected: selectKeyboard.value, + ), + ), + const SizedBox(width: 10), + Obx( + () => ToolbarIconButton( + tooltip: '表情', + onPressed: () { + if (selectKeyboard.value) { + selectKeyboard.value = false; + updatePanelType(PanelType.emoji); + } + }, + icon: const Icon(Icons.emoji_emotions, size: 22), + selected: !selectKeyboard.value, + ), + ), + const Spacer(), + Obx( + () => FilledButton.tonal( + onPressed: enablePublish.value ? onPublish : null, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + visualDensity: const VisualDensity( + horizontal: -2, + vertical: -2, + ), + ), + child: const Text('发送'), + ), + ), + ], + ), + ), + ], + ), + ); + } + + @override + Future onCustomPublish({ + required String message, + List? pictures, + int? dmType, + emoticonOptions, + }) async { + if (!liveRoomController.isLogin) { + SmartDialog.showToast('未登录'); + return; + } + final res = await LiveHttp.sendLiveMsg( + roomId: liveRoomController.roomId, + msg: message, + dmType: dmType, + emoticonOptions: emoticonOptions, + ); + if (res['status']) { + Get.back(); + liveRoomController.savedDanmaku = null; + SmartDialog.showToast('发送成功'); + liveRoomController.plPlayerController.danmakuController?.addDanmaku( + DanmakuContentItem( + message, + type: DanmakuItemType.scroll, + selfSend: true, + ), + ); + } else { + SmartDialog.showToast(res['msg']); + } + } +} diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 3baaee0f..7b5fc10f 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; -import 'package:PiliPlus/http/live.dart'; +import 'package:PiliPlus/pages/live_room/send_dm_panel.dart'; import 'package:PiliPlus/pages/live_room/widgets/chat.dart'; import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; import 'package:PiliPlus/services/service_locator.dart'; @@ -13,7 +13,6 @@ import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:floating/floating.dart'; 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:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/plugin/pl_player/index.dart'; @@ -44,8 +43,6 @@ class _LiveRoomPageState extends State bool isPlay = true; Floating? floating; - late final _node = FocusNode(); - late final _ctr = TextEditingController(); StreamSubscription? _listener; int latestAddedPosition = -1; @@ -128,10 +125,8 @@ class _LiveRoomPageState extends State PlPlayerController.setPlayCallBack(null); _liveRoomController.msgStream?.close(); // floating?.dispose(); - _node.dispose(); plPlayerController.removeStatusLister(playerListener); plPlayerController.dispose(); - _ctr.dispose(); super.dispose(); } @@ -157,66 +152,60 @@ class _LiveRoomPageState extends State plPlayerController.triggerFullScreen(status: false); } }, - child: Listener( - onPointerDown: (_) { - _node.unfocus(); - }, - child: FutureBuilder( - key: videoPlayerKey, - future: _futureBuilderFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData && snapshot.data['status']) { - return PLVideoPlayer( - key: playerKey, - fill: fill, - alignment: alignment, + child: FutureBuilder( + key: videoPlayerKey, + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && snapshot.data['status']) { + return PLVideoPlayer( + key: playerKey, + fill: fill, + alignment: alignment, + plPlayerController: plPlayerController, + headerControl: LiveHeaderControl( plPlayerController: plPlayerController, - headerControl: LiveHeaderControl( - plPlayerController: plPlayerController, - floating: floating, - onSendDanmaku: _liveRoomController.onSendDanmaku, - ), - bottomControl: BottomControl( - plPlayerController: plPlayerController, - liveRoomCtr: _liveRoomController, - onRefresh: () { - _futureBuilderFuture = _liveRoomController.queryLiveInfo(); - }, - ), - danmuWidget: Obx( - () => AnimatedOpacity( - opacity: plPlayerController.isOpenDanmu.value ? 1 : 0, - duration: const Duration(milliseconds: 100), - child: DanmakuScreen( - createdController: (DanmakuController e) { - plPlayerController.danmakuController = - _liveRoomController.controller = e; - }, - option: DanmakuOption( - fontSize: _getFontSize(isFullScreen), - fontWeight: plPlayerController.fontWeight, - area: plPlayerController.showArea, - opacity: plPlayerController.opacity, - hideTop: plPlayerController.blockTypes.contains(5), - hideScroll: plPlayerController.blockTypes.contains(2), - hideBottom: plPlayerController.blockTypes.contains(4), - duration: plPlayerController.danmakuDuration / - plPlayerController.playbackSpeed, - staticDuration: - plPlayerController.danmakuStaticDuration / - plPlayerController.playbackSpeed, - strokeWidth: plPlayerController.strokeWidth, - lineHeight: plPlayerController.danmakuLineHeight, - ), + floating: floating, + onSendDanmaku: onSendDanmaku, + ), + bottomControl: BottomControl( + plPlayerController: plPlayerController, + liveRoomCtr: _liveRoomController, + onRefresh: () { + _futureBuilderFuture = _liveRoomController.queryLiveInfo(); + }, + ), + danmuWidget: Obx( + () => AnimatedOpacity( + opacity: plPlayerController.isOpenDanmu.value ? 1 : 0, + duration: const Duration(milliseconds: 100), + child: DanmakuScreen( + createdController: (DanmakuController e) { + plPlayerController.danmakuController = + _liveRoomController.controller = e; + }, + option: DanmakuOption( + fontSize: _getFontSize(isFullScreen), + fontWeight: plPlayerController.fontWeight, + area: plPlayerController.showArea, + opacity: plPlayerController.opacity, + hideTop: plPlayerController.blockTypes.contains(5), + hideScroll: plPlayerController.blockTypes.contains(2), + hideBottom: plPlayerController.blockTypes.contains(4), + duration: plPlayerController.danmakuDuration / + plPlayerController.playbackSpeed, + staticDuration: plPlayerController.danmakuStaticDuration / + plPlayerController.playbackSpeed, + strokeWidth: plPlayerController.strokeWidth, + lineHeight: plPlayerController.danmakuLineHeight, ), ), ), - ); - } else { - return const SizedBox(); - } - }, - ), + ), + ); + } else { + return const SizedBox(); + } + }, ), ); } @@ -361,7 +350,7 @@ class _LiveRoomPageState extends State ); } - Color get _color => Color(0xFFEEEEEE); + final Color _color = Color(0xFFEEEEEE); PreferredSizeWidget get _buildAppBar => AppBar( backgroundColor: Colors.transparent, @@ -381,7 +370,6 @@ class _LiveRoomPageState extends State children: [ GestureDetector( onTap: () { - _node.unfocus(); dynamic uid = _liveRoomController.roomInfoH5.value.roomInfo?.uid; Get.toNamed( @@ -481,15 +469,12 @@ class _LiveRoomPageState extends State ), ), Expanded( - child: Scaffold( - backgroundColor: Colors.transparent, - body: SafeArea( - left: false, - top: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBottomWidget, - ), + child: SafeArea( + left: false, + top: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildBottomWidget, ), ), ), @@ -517,18 +502,13 @@ class _LiveRoomPageState extends State _buildInputWidget, ]; - Widget _buildChatWidget([bool? isPP]) => Listener( - onPointerDown: (_) { - _node.unfocus(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: LiveRoomChat( - key: chatKey, - isPP: isPP, - roomId: _roomId, - liveRoomController: _liveRoomController, - ), + Widget _buildChatWidget([bool? isPP]) => Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: LiveRoomChat( + key: chatKey, + isPP: isPP, + roomId: _roomId, + liveRoomController: _liveRoomController, ), ); @@ -568,60 +548,43 @@ class _LiveRoomPageState extends State ), ), Expanded( - child: TextField( - focusNode: _node, - controller: _ctr, - textInputAction: TextInputAction.send, - cursorColor: _color, - style: TextStyle(color: _color), - onSubmitted: (value) { - if (value.isNotEmpty) { - _onSendMsg(value); - } - }, - decoration: InputDecoration( - border: InputBorder.none, - hintText: '发送弹幕', - hintStyle: TextStyle( - color: Colors.white.withOpacity(0.6), - ), + child: GestureDetector( + onTap: onSendDanmaku, + child: Text( + '发送弹幕', + style: TextStyle(color: _color), ), ), ), IconButton( onPressed: () { - if (_ctr.text.isNotEmpty) { - _onSendMsg(_ctr.text); - } + onSendDanmaku(true); }, - icon: Icon(Icons.send, color: _color), + icon: Icon(Icons.emoji_emotions_outlined, color: _color), ), ], ), ); - void _onSendMsg(msg) async { - if (!_liveRoomController.isLogin) { - SmartDialog.showToast('未登录'); - return; - } - dynamic res = await LiveHttp.sendLiveMsg( - roomId: _liveRoomController.roomId, msg: msg); - if (res['status']) { - if (mounted) { - FocusScope.of(context).unfocus(); - } - SmartDialog.showToast('发送成功'); - plPlayerController.danmakuController?.addDanmaku( - DanmakuContentItem( - _ctr.text, - type: DanmakuItemType.scroll, - selfSend: true, - ), - ); - _ctr.clear(); - } else { - SmartDialog.showToast(res['msg']); - } + void onSendDanmaku([bool fromEmote = false]) { + Get.generalDialog( + pageBuilder: (context, animation, secondaryAnimation) { + return LiveSendDmPanel( + fromEmote: fromEmote, + liveRoomController: _liveRoomController, + initialValue: _liveRoomController.savedDanmaku, + onSave: (msg) => _liveRoomController.savedDanmaku = msg, + ); + }, + transitionDuration: const Duration(milliseconds: 500), + transitionBuilder: (context, animation, secondaryAnimation, child) { + var tween = Tween(begin: Offset(0.0, 1.0), end: Offset.zero) + .chain(CurveTween(curve: Curves.linear)); + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + ); } } diff --git a/lib/pages/video/detail/reply_new/index.dart b/lib/pages/video/detail/reply_new/index.dart deleted file mode 100644 index f8d6519e..00000000 --- a/lib/pages/video/detail/reply_new/index.dart +++ /dev/null @@ -1,3 +0,0 @@ -library video_reply_new; - -export './view.dart'; diff --git a/lib/pages/video/detail/reply_new/view.dart b/lib/pages/video/detail/reply_new/view.dart deleted file mode 100644 index 9ed6928a..00000000 --- a/lib/pages/video/detail/reply_new/view.dart +++ /dev/null @@ -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 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 (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, - ) - ], - ), - ); - } -} diff --git a/lib/pages/video/detail/widgets/send_danmaku_panel.dart b/lib/pages/video/detail/widgets/send_danmaku_panel.dart index a22b3cfb..80fd114e 100644 --- a/lib/pages/video/detail/widgets/send_danmaku_panel.dart +++ b/lib/pages/video/detail/widgets/send_danmaku_panel.dart @@ -19,9 +19,6 @@ class SendDanmakuPanel extends CommonPublishPage { final dynamic bvid; final dynamic progress; - // live - final dynamic roomId; - final ValueChanged 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 { 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 { ), 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 { @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();