mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: send live emote
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -757,4 +757,7 @@ class Api {
|
|||||||
static const String replyTop = '/x/v2/reply/top';
|
static const String replyTop = '/x/v2/reply/top';
|
||||||
|
|
||||||
static const String getCoin = '${HttpString.accountBaseUrl}/site/getCoin';
|
static const String getCoin = '${HttpString.accountBaseUrl}/site/getCoin';
|
||||||
|
|
||||||
|
static const String getLiveEmoticons =
|
||||||
|
'${HttpString.liveBaseUrl}/xlive/web-ucenter/v2/emoticon/GetEmoticons';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:PiliPlus/common/constants.dart';
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/live/danmu_info.dart';
|
import 'package:PiliPlus/models/live/danmu_info.dart';
|
||||||
import 'package:PiliPlus/models/live/follow.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 'package:dio/dio.dart';
|
||||||
import '../models/live/item.dart';
|
import '../models/live/item.dart';
|
||||||
import '../models/live/room_info.dart';
|
import '../models/live/room_info.dart';
|
||||||
@@ -24,35 +26,34 @@ class LiveHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future sendLiveMsg({
|
static Future sendLiveMsg({roomId, msg, dmType, emoticonOptions}) async {
|
||||||
roomId,
|
|
||||||
msg,
|
|
||||||
}) async {
|
|
||||||
dynamic csrf = await Request.getCsrf();
|
dynamic csrf = await Request.getCsrf();
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.sendLiveMsg,
|
Api.sendLiveMsg,
|
||||||
data: {
|
data: FormData.fromMap({
|
||||||
'bubble': 0,
|
'bubble': 0,
|
||||||
'msg': msg,
|
'msg': msg,
|
||||||
'color': 16777215,
|
'color': 16777215,
|
||||||
'mode': 1,
|
'mode': 1,
|
||||||
|
if (dmType != null) 'dm_type': dmType,
|
||||||
|
if (emoticonOptions != null)
|
||||||
|
'emoticonOptions': emoticonOptions
|
||||||
|
else ...{
|
||||||
'room_type': 0,
|
'room_type': 0,
|
||||||
'jumpfrom': 71000,
|
'jumpfrom': 0,
|
||||||
'reply_mid': 0,
|
'reply_mid': 0,
|
||||||
'reply_attr': 0,
|
'reply_attr': 0,
|
||||||
'replay_dmid': '',
|
'replay_dmid': '',
|
||||||
'statistics': Constants.statistics,
|
'statistics': Constants.statistics,
|
||||||
'reply_type': 0,
|
'reply_type': 0,
|
||||||
'reply_uname': '',
|
'reply_uname': '',
|
||||||
|
},
|
||||||
'fontsize': 25,
|
'fontsize': 25,
|
||||||
'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
'roomid': roomId,
|
'roomid': roomId,
|
||||||
'csrf': csrf,
|
'csrf': csrf,
|
||||||
'csrf_token': csrf,
|
'csrf_token': csrf,
|
||||||
},
|
}),
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@@ -146,4 +147,21 @@ class LiveHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<List<LiveEmoteDatum>?>> 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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/models/live/live_emoticons/data.dart
Normal file
23
lib/models/live/live_emoticons/data.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'datum.dart';
|
||||||
|
|
||||||
|
class LiveEmoteData {
|
||||||
|
int? fansBrand;
|
||||||
|
List<LiveEmoteDatum>? data;
|
||||||
|
dynamic purchaseUrl;
|
||||||
|
|
||||||
|
LiveEmoteData({this.fansBrand, this.data, this.purchaseUrl});
|
||||||
|
|
||||||
|
factory LiveEmoteData.fromJson(Map<String, dynamic> json) => LiveEmoteData(
|
||||||
|
fansBrand: json['fans_brand'] as int?,
|
||||||
|
data: (json['data'] as List<dynamic>?)
|
||||||
|
?.map((e) => LiveEmoteDatum.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
purchaseUrl: json['purchase_url'] as dynamic,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'fans_brand': fansBrand,
|
||||||
|
'data': data?.map((e) => e.toJson()).toList(),
|
||||||
|
'purchase_url': purchaseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
71
lib/models/live/live_emoticons/datum.dart
Normal file
71
lib/models/live/live_emoticons/datum.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'emoticon.dart';
|
||||||
|
import 'top_show.dart';
|
||||||
|
import 'top_show_recent.dart';
|
||||||
|
|
||||||
|
class LiveEmoteDatum {
|
||||||
|
List<LiveEmoticon>? emoticons;
|
||||||
|
int? pkgId;
|
||||||
|
String? pkgName;
|
||||||
|
int? pkgType;
|
||||||
|
String? pkgDescript;
|
||||||
|
int? pkgPerm;
|
||||||
|
int? unlockIdentity;
|
||||||
|
int? unlockNeedGift;
|
||||||
|
String? currentCover;
|
||||||
|
List<dynamic>? 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<String, dynamic> json) => LiveEmoteDatum(
|
||||||
|
emoticons: (json['emoticons'] as List<dynamic>?)
|
||||||
|
?.map((e) => LiveEmoticon.fromJson(e as Map<String, dynamic>))
|
||||||
|
.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<dynamic>?,
|
||||||
|
topShow: json['top_show'] == null
|
||||||
|
? null
|
||||||
|
: TopShow.fromJson(json['top_show'] as Map<String, dynamic>),
|
||||||
|
topShowRecent: json['top_show_recent'] == null
|
||||||
|
? null
|
||||||
|
: TopShowRecent.fromJson(
|
||||||
|
json['top_show_recent'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> 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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
83
lib/models/live/live_emoticons/emoticon.dart
Normal file
83
lib/models/live/live_emoticons/emoticon.dart
Normal file
@@ -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<String, dynamic> 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<String, dynamic> 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
26
lib/models/live/live_emoticons/live_emoticons.dart
Normal file
26
lib/models/live/live_emoticons/live_emoticons.dart
Normal file
@@ -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<String, dynamic> 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<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'code': code,
|
||||||
|
'message': message,
|
||||||
|
'ttl': ttl,
|
||||||
|
'data': data?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
16
lib/models/live/live_emoticons/top_left.dart
Normal file
16
lib/models/live/live_emoticons/top_left.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class TopLeft {
|
||||||
|
String? image;
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
TopLeft({this.image, this.text});
|
||||||
|
|
||||||
|
factory TopLeft.fromJson(Map<String, dynamic> json) => TopLeft(
|
||||||
|
image: json['image'] as String?,
|
||||||
|
text: json['text'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'image': image,
|
||||||
|
'text': text,
|
||||||
|
};
|
||||||
|
}
|
||||||
16
lib/models/live/live_emoticons/top_right.dart
Normal file
16
lib/models/live/live_emoticons/top_right.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class TopRight {
|
||||||
|
String? image;
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
TopRight({this.image, this.text});
|
||||||
|
|
||||||
|
factory TopRight.fromJson(Map<String, dynamic> json) => TopRight(
|
||||||
|
image: json['image'] as String?,
|
||||||
|
text: json['text'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'image': image,
|
||||||
|
'text': text,
|
||||||
|
};
|
||||||
|
}
|
||||||
23
lib/models/live/live_emoticons/top_show.dart
Normal file
23
lib/models/live/live_emoticons/top_show.dart
Normal file
@@ -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<String, dynamic> json) => TopShow(
|
||||||
|
topLeft: json['top_left'] == null
|
||||||
|
? null
|
||||||
|
: TopLeft.fromJson(json['top_left'] as Map<String, dynamic>),
|
||||||
|
topRight: json['top_right'] == null
|
||||||
|
? null
|
||||||
|
: TopRight.fromJson(json['top_right'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'top_left': topLeft?.toJson(),
|
||||||
|
'top_right': topRight?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
23
lib/models/live/live_emoticons/top_show_recent.dart
Normal file
23
lib/models/live/live_emoticons/top_show_recent.dart
Normal file
@@ -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<String, dynamic> json) => TopShowRecent(
|
||||||
|
topLeft: json['top_left'] == null
|
||||||
|
? null
|
||||||
|
: TopLeft.fromJson(json['top_left'] as Map<String, dynamic>),
|
||||||
|
topRight: json['top_right'] == null
|
||||||
|
? null
|
||||||
|
: TopRight.fromJson(json['top_right'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'top_left': topLeft?.toJson(),
|
||||||
|
'top_right': topRight?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/icon_button.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
|
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
|
||||||
show SourceModel, SourceType;
|
show SourceModel, SourceType;
|
||||||
import 'package:PiliPlus/http/msg.dart';
|
import 'package:PiliPlus/http/msg.dart';
|
||||||
|
import 'package:PiliPlus/models/live/live_emoticons/emoticon.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -197,18 +198,29 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
|
|||||||
|
|
||||||
Future onCustomPublish({required String message, List? pictures});
|
Future onCustomPublish({required String message, List? pictures});
|
||||||
|
|
||||||
void onChooseEmote(Packages package, Emote emote) {
|
void onChooseEmote(emote) {
|
||||||
enablePublish.value = true;
|
enablePublish.value = true;
|
||||||
final int cursorPosition = editController.selection.baseOffset;
|
final int cursorPosition = editController.selection.baseOffset;
|
||||||
final String currentText = editController.text;
|
final String currentText = editController.text;
|
||||||
|
if (emote is Emote) {
|
||||||
final String newText = currentText.substring(0, cursorPosition) +
|
final String newText = currentText.substring(0, cursorPosition) +
|
||||||
emote.text! +
|
emote.text! +
|
||||||
currentText.substring(cursorPosition);
|
currentText.substring(cursorPosition);
|
||||||
editController.value = TextEditingValue(
|
editController.value = TextEditingValue(
|
||||||
text: newText,
|
text: newText,
|
||||||
selection:
|
selection: TextSelection.collapsed(
|
||||||
TextSelection.collapsed(offset: cursorPosition + emote.text!.length),
|
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);
|
widget.onSave?.call(editController.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'controller.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
|
|
||||||
class EmotePanel extends StatefulWidget {
|
class EmotePanel extends StatefulWidget {
|
||||||
final Function onChoose;
|
final ValueChanged<Emote> onChoose;
|
||||||
const EmotePanel({super.key, required this.onChoose});
|
const EmotePanel({super.key, required this.onChoose});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -60,7 +60,7 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.onChoose(e, e.emote![index]);
|
widget.onChoose(e.emote![index]);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
|
|||||||
41
lib/pages/live_emote/controller.dart
Normal file
41
lib/pages/live_emote/controller.dart
Normal file
@@ -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<List<LiveEmoteDatum>?, LiveEmoteDatum>
|
||||||
|
with GetTickerProviderStateMixin {
|
||||||
|
LiveEmotePanelController(this.roomId);
|
||||||
|
final int roomId;
|
||||||
|
TabController? tabController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
queryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool customHandleResponse(
|
||||||
|
bool isRefresh, Success<List<LiveEmoteDatum>?> response) {
|
||||||
|
if (response.response?.isNotEmpty == true) {
|
||||||
|
tabController =
|
||||||
|
TabController(length: response.response!.length, vsync: this);
|
||||||
|
}
|
||||||
|
loadingState.value = response;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoadingState<List<LiveEmoteDatum>?>> customGetData() =>
|
||||||
|
LiveHttp.getLiveEmoticons(roomId: roomId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
tabController?.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/live_emote/index.dart
Normal file
4
lib/pages/live_emote/index.dart
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
library emote;
|
||||||
|
|
||||||
|
export 'controller.dart';
|
||||||
|
export 'view.dart';
|
||||||
148
lib/pages/live_emote/view.dart
Normal file
148
lib/pages/live_emote/view.dart
Normal file
@@ -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<LiveEmoticon> onChoose;
|
||||||
|
final ValueChanged<LiveEmoticon> onSendEmoticonUnique;
|
||||||
|
const LiveEmotePanel({
|
||||||
|
super.key,
|
||||||
|
required this.roomId,
|
||||||
|
required this.onChoose,
|
||||||
|
required this.onSendEmoticonUnique,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LiveEmotePanel> createState() => _LiveEmotePanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LiveEmotePanelState extends State<LiveEmotePanel>
|
||||||
|
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<List<LiveEmoteDatum>?> 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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import 'package:PiliPlus/http/video.dart';
|
|||||||
import 'package:PiliPlus/models/live/danmu_info.dart';
|
import 'package:PiliPlus/models/live/danmu_info.dart';
|
||||||
import 'package:PiliPlus/models/live/quality.dart';
|
import 'package:PiliPlus/models/live/quality.dart';
|
||||||
import 'package:PiliPlus/pages/mine/controller.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/services/service_locator.dart';
|
||||||
import 'package:PiliPlus/tcp/live.dart';
|
import 'package:PiliPlus/tcp/live.dart';
|
||||||
import 'package:PiliPlus/utils/danmaku.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:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/http/constants.dart';
|
import 'package:PiliPlus/http/constants.dart';
|
||||||
import 'package:PiliPlus/http/live.dart';
|
import 'package:PiliPlus/http/live.dart';
|
||||||
import 'package:PiliPlus/models/live/room_info.dart';
|
import 'package:PiliPlus/models/live/room_info.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/index.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 '../../models/live/room_info_h5.dart';
|
||||||
import '../../utils/video_utils.dart';
|
import '../../utils/video_utils.dart';
|
||||||
|
|
||||||
@@ -248,41 +245,4 @@ class LiveRoomController extends GetxController {
|
|||||||
.description;
|
.description;
|
||||||
await queryLiveInfo();
|
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
247
lib/pages/live_room/send_dm_panel.dart
Normal file
247
lib/pages/live_room/send_dm_panel.dart
Normal file
@@ -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<LiveSendDmPanel> createState() => _ReplyPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReplyPageState extends CommonPublishPageState<LiveSendDmPanel> {
|
||||||
|
LiveRoomController get liveRoomController => widget.liveRoomController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.fromEmote) {
|
||||||
|
selectKeyboard.value = false;
|
||||||
|
updatePanelType(PanelType.emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Get.delete<LiveEmotePanelController>(
|
||||||
|
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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
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/chat.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
|
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
|
||||||
import 'package:PiliPlus/services/service_locator.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:floating/floating.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||||
@@ -44,8 +43,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
bool isPlay = true;
|
bool isPlay = true;
|
||||||
Floating? floating;
|
Floating? floating;
|
||||||
|
|
||||||
late final _node = FocusNode();
|
|
||||||
late final _ctr = TextEditingController();
|
|
||||||
StreamSubscription? _listener;
|
StreamSubscription? _listener;
|
||||||
|
|
||||||
int latestAddedPosition = -1;
|
int latestAddedPosition = -1;
|
||||||
@@ -128,10 +125,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
PlPlayerController.setPlayCallBack(null);
|
PlPlayerController.setPlayCallBack(null);
|
||||||
_liveRoomController.msgStream?.close();
|
_liveRoomController.msgStream?.close();
|
||||||
// floating?.dispose();
|
// floating?.dispose();
|
||||||
_node.dispose();
|
|
||||||
plPlayerController.removeStatusLister(playerListener);
|
plPlayerController.removeStatusLister(playerListener);
|
||||||
plPlayerController.dispose();
|
plPlayerController.dispose();
|
||||||
_ctr.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +152,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
plPlayerController.triggerFullScreen(status: false);
|
plPlayerController.triggerFullScreen(status: false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Listener(
|
|
||||||
onPointerDown: (_) {
|
|
||||||
_node.unfocus();
|
|
||||||
},
|
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
key: videoPlayerKey,
|
key: videoPlayerKey,
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
@@ -174,7 +165,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
headerControl: LiveHeaderControl(
|
headerControl: LiveHeaderControl(
|
||||||
plPlayerController: plPlayerController,
|
plPlayerController: plPlayerController,
|
||||||
floating: floating,
|
floating: floating,
|
||||||
onSendDanmaku: _liveRoomController.onSendDanmaku,
|
onSendDanmaku: onSendDanmaku,
|
||||||
),
|
),
|
||||||
bottomControl: BottomControl(
|
bottomControl: BottomControl(
|
||||||
plPlayerController: plPlayerController,
|
plPlayerController: plPlayerController,
|
||||||
@@ -202,8 +193,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
hideBottom: plPlayerController.blockTypes.contains(4),
|
hideBottom: plPlayerController.blockTypes.contains(4),
|
||||||
duration: plPlayerController.danmakuDuration /
|
duration: plPlayerController.danmakuDuration /
|
||||||
plPlayerController.playbackSpeed,
|
plPlayerController.playbackSpeed,
|
||||||
staticDuration:
|
staticDuration: plPlayerController.danmakuStaticDuration /
|
||||||
plPlayerController.danmakuStaticDuration /
|
|
||||||
plPlayerController.playbackSpeed,
|
plPlayerController.playbackSpeed,
|
||||||
strokeWidth: plPlayerController.strokeWidth,
|
strokeWidth: plPlayerController.strokeWidth,
|
||||||
lineHeight: plPlayerController.danmakuLineHeight,
|
lineHeight: plPlayerController.danmakuLineHeight,
|
||||||
@@ -217,7 +207,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +350,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get _color => Color(0xFFEEEEEE);
|
final Color _color = Color(0xFFEEEEEE);
|
||||||
|
|
||||||
PreferredSizeWidget get _buildAppBar => AppBar(
|
PreferredSizeWidget get _buildAppBar => AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@@ -381,7 +370,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_node.unfocus();
|
|
||||||
dynamic uid =
|
dynamic uid =
|
||||||
_liveRoomController.roomInfoH5.value.roomInfo?.uid;
|
_liveRoomController.roomInfoH5.value.roomInfo?.uid;
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
@@ -481,9 +469,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Scaffold(
|
child: SafeArea(
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: SafeArea(
|
|
||||||
left: false,
|
left: false,
|
||||||
top: false,
|
top: false,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -492,7 +478,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -517,11 +502,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
_buildInputWidget,
|
_buildInputWidget,
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildChatWidget([bool? isPP]) => Listener(
|
Widget _buildChatWidget([bool? isPP]) => Padding(
|
||||||
onPointerDown: (_) {
|
|
||||||
_node.unfocus();
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: LiveRoomChat(
|
child: LiveRoomChat(
|
||||||
key: chatKey,
|
key: chatKey,
|
||||||
@@ -529,7 +510,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
roomId: _roomId,
|
roomId: _roomId,
|
||||||
liveRoomController: _liveRoomController,
|
liveRoomController: _liveRoomController,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget get _buildInputWidget => Container(
|
Widget get _buildInputWidget => Container(
|
||||||
@@ -568,60 +548,43 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: GestureDetector(
|
||||||
focusNode: _node,
|
onTap: onSendDanmaku,
|
||||||
controller: _ctr,
|
child: Text(
|
||||||
textInputAction: TextInputAction.send,
|
'发送弹幕',
|
||||||
cursorColor: _color,
|
|
||||||
style: TextStyle(color: _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),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_ctr.text.isNotEmpty) {
|
onSendDanmaku(true);
|
||||||
_onSendMsg(_ctr.text);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.send, color: _color),
|
icon: Icon(Icons.emoji_emotions_outlined, color: _color),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
void _onSendMsg(msg) async {
|
void onSendDanmaku([bool fromEmote = false]) {
|
||||||
if (!_liveRoomController.isLogin) {
|
Get.generalDialog(
|
||||||
SmartDialog.showToast('未登录');
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
return;
|
return LiveSendDmPanel(
|
||||||
}
|
fromEmote: fromEmote,
|
||||||
dynamic res = await LiveHttp.sendLiveMsg(
|
liveRoomController: _liveRoomController,
|
||||||
roomId: _liveRoomController.roomId, msg: msg);
|
initialValue: _liveRoomController.savedDanmaku,
|
||||||
if (res['status']) {
|
onSave: (msg) => _liveRoomController.savedDanmaku = msg,
|
||||||
if (mounted) {
|
);
|
||||||
FocusScope.of(context).unfocus();
|
},
|
||||||
}
|
transitionDuration: const Duration(milliseconds: 500),
|
||||||
SmartDialog.showToast('发送成功');
|
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
plPlayerController.danmakuController?.addDanmaku(
|
var tween = Tween(begin: Offset(0.0, 1.0), end: Offset.zero)
|
||||||
DanmakuContentItem(
|
.chain(CurveTween(curve: Curves.linear));
|
||||||
_ctr.text,
|
return SlideTransition(
|
||||||
type: DanmakuItemType.scroll,
|
position: animation.drive(tween),
|
||||||
selfSend: true,
|
child: child,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
_ctr.clear();
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
library video_reply_new;
|
|
||||||
|
|
||||||
export './view.dart';
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:PiliPlus/http/video.dart';
|
|
||||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
|
||||||
import 'package:PiliPlus/models/video/reply/emote.dart';
|
|
||||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
|
||||||
import 'package:PiliPlus/pages/emote/index.dart';
|
|
||||||
import 'package:PiliPlus/utils/feed_back.dart';
|
|
||||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
|
||||||
|
|
||||||
import 'toolbar_icon_button.dart';
|
|
||||||
|
|
||||||
@Deprecated('Use ReplyPage instead')
|
|
||||||
class VideoReplyNewDialog extends StatefulWidget {
|
|
||||||
final int? oid;
|
|
||||||
final int? root;
|
|
||||||
final int? parent;
|
|
||||||
final ReplyType? replyType;
|
|
||||||
final ReplyItemModel? replyItem;
|
|
||||||
|
|
||||||
const VideoReplyNewDialog({
|
|
||||||
super.key,
|
|
||||||
this.oid,
|
|
||||||
this.root,
|
|
||||||
this.parent,
|
|
||||||
this.replyType,
|
|
||||||
this.replyItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<VideoReplyNewDialog> createState() => _VideoReplyNewDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
|
||||||
with WidgetsBindingObserver {
|
|
||||||
final TextEditingController _replyContentController = TextEditingController();
|
|
||||||
final FocusNode replyContentFocusNode = FocusNode();
|
|
||||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
|
||||||
late double emoteHeight = 0.0;
|
|
||||||
double keyboardHeight = 0.0; // 键盘高度
|
|
||||||
final _debouncer =
|
|
||||||
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
|
|
||||||
String toolbarType = 'input';
|
|
||||||
bool _enablePublish = false;
|
|
||||||
final _publishStream = StreamController<bool>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// 监听输入框聚焦
|
|
||||||
// replyContentFocusNode.addListener(_onFocus);
|
|
||||||
// 界面观察者 必须
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
// 自动聚焦
|
|
||||||
_autoFocus();
|
|
||||||
// 监听聚焦状态
|
|
||||||
_focusListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
_autoFocus() async {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
if (mounted) {
|
|
||||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_focusListener() {
|
|
||||||
replyContentFocusNode.addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void listener() {
|
|
||||||
if (replyContentFocusNode.hasFocus) {
|
|
||||||
setState(() {
|
|
||||||
toolbarType = 'input';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future submitReplyAdd() async {
|
|
||||||
feedBack();
|
|
||||||
String message = _replyContentController.text;
|
|
||||||
var result = await VideoHttp.replyAdd(
|
|
||||||
type: widget.replyType ?? ReplyType.video,
|
|
||||||
oid: widget.oid!,
|
|
||||||
root: widget.root!,
|
|
||||||
parent: widget.parent!,
|
|
||||||
message: widget.replyItem != null && widget.replyItem!.root != 0
|
|
||||||
? ' 回复 @${widget.replyItem!.member!.uname!} : $message'
|
|
||||||
: message,
|
|
||||||
);
|
|
||||||
if (result['status']) {
|
|
||||||
SmartDialog.showToast(result['data']['success_toast']);
|
|
||||||
Get.back(result: {
|
|
||||||
'data': ReplyItemModel.fromJson(result['data']['reply'], ''),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(result['msg']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChooseEmote(Packages package, Emote emote) {
|
|
||||||
if (!_enablePublish) {
|
|
||||||
_enablePublish = true;
|
|
||||||
_publishStream.add(true);
|
|
||||||
}
|
|
||||||
final int cursorPosition = _replyContentController.selection.baseOffset;
|
|
||||||
final String currentText = _replyContentController.text;
|
|
||||||
final String newText = currentText.substring(0, cursorPosition) +
|
|
||||||
emote.text! +
|
|
||||||
currentText.substring(cursorPosition);
|
|
||||||
_replyContentController.value = TextEditingValue(
|
|
||||||
text: newText,
|
|
||||||
selection:
|
|
||||||
TextSelection.collapsed(offset: cursorPosition + emote.text!.length),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeMetrics() {
|
|
||||||
super.didChangeMetrics();
|
|
||||||
if (!mounted) return;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (!mounted) return;
|
|
||||||
// 键盘高度
|
|
||||||
final viewInsets = EdgeInsets.fromViewPadding(
|
|
||||||
View.of(context).viewInsets, View.of(context).devicePixelRatio);
|
|
||||||
_debouncer(() {
|
|
||||||
if (!mounted) return;
|
|
||||||
if (keyboardHeight == 0 && emoteHeight == 0) {
|
|
||||||
emoteHeight = keyboardHeight =
|
|
||||||
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
|
|
||||||
if (emoteHeight < 200) emoteHeight = 200;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_publishStream.close();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
_replyContentController.dispose();
|
|
||||||
replyContentFocusNode.removeListener(listener);
|
|
||||||
replyContentFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
double _keyboardHeight = EdgeInsets.fromViewPadding(
|
|
||||||
View.of(context).viewInsets, View.of(context).devicePixelRatio)
|
|
||||||
.bottom;
|
|
||||||
return Container(
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(12),
|
|
||||||
topRight: Radius.circular(12),
|
|
||||||
),
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(top: 12, right: 15, left: 15, bottom: 10),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
||||||
child: TextField(
|
|
||||||
controller: _replyContentController,
|
|
||||||
minLines: 4,
|
|
||||||
maxLines: 8,
|
|
||||||
autofocus: false,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value.isNotEmpty && !_enablePublish) {
|
|
||||||
_enablePublish = true;
|
|
||||||
_publishStream.add(true);
|
|
||||||
} else if (value.isEmpty && _enablePublish) {
|
|
||||||
_enablePublish = false;
|
|
||||||
_publishStream.add(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
focusNode: replyContentFocusNode,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: "输入回复内容",
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
)),
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(
|
|
||||||
height: 1,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 52,
|
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
ToolbarIconButton(
|
|
||||||
tooltip: '输入',
|
|
||||||
onPressed: () {
|
|
||||||
if (toolbarType == 'emote') {
|
|
||||||
setState(() {
|
|
||||||
toolbarType = 'input';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.keyboard, size: 22),
|
|
||||||
// toolbarType: toolbarType,
|
|
||||||
selected: toolbarType == 'input',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
ToolbarIconButton(
|
|
||||||
tooltip: '表情',
|
|
||||||
onPressed: () {
|
|
||||||
if (toolbarType == 'input') {
|
|
||||||
setState(() {
|
|
||||||
toolbarType = 'emote';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.emoji_emotions, size: 22),
|
|
||||||
// toolbarType: toolbarType,
|
|
||||||
selected: toolbarType == 'emote',
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
StreamBuilder(
|
|
||||||
initialData: false,
|
|
||||||
stream: _publishStream.stream.distinct(),
|
|
||||||
builder: (context, snapshot) => FilledButton.tonal(
|
|
||||||
onPressed: snapshot.data == true ? submitReplyAdd : null,
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20, vertical: 10),
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
horizontal: -2,
|
|
||||||
vertical: -2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('发送'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedSize(
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: toolbarType == 'input'
|
|
||||||
? (_keyboardHeight > keyboardHeight
|
|
||||||
? _keyboardHeight
|
|
||||||
: keyboardHeight)
|
|
||||||
: emoteHeight,
|
|
||||||
child: EmotePanel(onChoose: onChooseEmote),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (toolbarType == 'input' && keyboardHeight == 0.0)
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: MediaQuery.of(context).padding.bottom,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,9 +19,6 @@ class SendDanmakuPanel extends CommonPublishPage {
|
|||||||
final dynamic bvid;
|
final dynamic bvid;
|
||||||
final dynamic progress;
|
final dynamic progress;
|
||||||
|
|
||||||
// live
|
|
||||||
final dynamic roomId;
|
|
||||||
|
|
||||||
final ValueChanged<DanmakuContentItem> callback;
|
final ValueChanged<DanmakuContentItem> callback;
|
||||||
final bool darkVideoPage;
|
final bool darkVideoPage;
|
||||||
|
|
||||||
@@ -36,7 +33,6 @@ class SendDanmakuPanel extends CommonPublishPage {
|
|||||||
this.cid,
|
this.cid,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.progress,
|
this.progress,
|
||||||
this.roomId,
|
|
||||||
required this.callback,
|
required this.callback,
|
||||||
required this.darkVideoPage,
|
required this.darkVideoPage,
|
||||||
this.dmConfig,
|
this.dmConfig,
|
||||||
@@ -86,18 +82,18 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
|||||||
|
|
||||||
get _buildColorPanel => Expanded(
|
get _buildColorPanel => Expanded(
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => LayoutBuilder(
|
() => Builder(
|
||||||
key: ValueKey(_color.value),
|
key: ValueKey(_color.value),
|
||||||
builder: (context, constraints) {
|
builder: (context) {
|
||||||
final int crossAxisCount = (constraints.maxWidth / 40).toInt();
|
|
||||||
final bool isCustomColor = _colorList.contains(_color.value).not;
|
final bool isCustomColor = _colorList.contains(_color.value).not;
|
||||||
final int length =
|
final int length =
|
||||||
_colorList.length + (isCustomColor ? 1 : 0) + 1;
|
_colorList.length + (isCustomColor ? 1 : 0) + 1;
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
crossAxisCount: crossAxisCount,
|
maxCrossAxisExtent: 40,
|
||||||
|
mainAxisExtent: 40,
|
||||||
crossAxisSpacing: 4,
|
crossAxisSpacing: 4,
|
||||||
mainAxisSpacing: 4,
|
mainAxisSpacing: 4,
|
||||||
),
|
),
|
||||||
@@ -341,7 +337,6 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.roomId == null)
|
|
||||||
Obx(
|
Obx(
|
||||||
() => iconButton(
|
() => iconButton(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -470,12 +465,7 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
|||||||
@override
|
@override
|
||||||
Future onCustomPublish({required String message, List? pictures}) async {
|
Future onCustomPublish({required String message, List? pictures}) async {
|
||||||
SmartDialog.showLoading(msg: '发送中...');
|
SmartDialog.showLoading(msg: '发送中...');
|
||||||
final res = widget.roomId != null
|
final res = await DanmakuHttp.shootDanmaku(
|
||||||
? await LiveHttp.sendLiveMsg(
|
|
||||||
roomId: widget.roomId,
|
|
||||||
msg: editController.text,
|
|
||||||
)
|
|
||||||
: await DanmakuHttp.shootDanmaku(
|
|
||||||
oid: widget.cid,
|
oid: widget.cid,
|
||||||
bvid: widget.bvid,
|
bvid: widget.bvid,
|
||||||
progress: widget.progress,
|
progress: widget.progress,
|
||||||
|
|||||||
Reference in New Issue
Block a user