mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-06 02:30:12 +08:00
feat: super chat
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] SuperChat
|
||||||
- [x] 播放课堂视频
|
- [x] 播放课堂视频
|
||||||
- [x] 发起投票
|
- [x] 发起投票
|
||||||
- [x] 发布动态/评论支持`富文本编辑`/`表情显示`/`@用户`
|
- [x] 发布动态/评论支持`富文本编辑`/`表情显示`/`@用户`
|
||||||
|
|||||||
@@ -962,4 +962,7 @@ class Api {
|
|||||||
|
|
||||||
static const String spaceShop =
|
static const String spaceShop =
|
||||||
'${HttpString.mallBaseUrl}/community-hub/small_shop/feed/tab/item';
|
'${HttpString.mallBaseUrl}/community-hub/small_shop/feed/tab/item';
|
||||||
|
|
||||||
|
static const String superChatMsg =
|
||||||
|
'${HttpString.liveBaseUrl}/av/v1/SuperChat/getMessageList';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
|||||||
import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart';
|
import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_search/data.dart';
|
import 'package:PiliPlus/models_new/live/live_search/data.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_second_list/data.dart';
|
import 'package:PiliPlus/models_new/live/live_second_list/data.dart';
|
||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/data.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/app_sign.dart';
|
import 'package:PiliPlus/utils/app_sign.dart';
|
||||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||||
@@ -642,4 +643,24 @@ class LiveHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<SuperChatData>> superChatMsg(
|
||||||
|
dynamic roomId,
|
||||||
|
) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.superChatMsg,
|
||||||
|
queryParameters: {
|
||||||
|
'room_id': roomId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return Success(SuperChatData.fromJson(res.data['data']));
|
||||||
|
} catch (e) {
|
||||||
|
return Error(e.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Error(res.data['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
lib/models_new/live/live_superchat/data.dart
Normal file
13
lib/models_new/live/live_superchat/data.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
|
|
||||||
|
class SuperChatData {
|
||||||
|
List<SuperChatItem>? list;
|
||||||
|
|
||||||
|
SuperChatData({this.list});
|
||||||
|
|
||||||
|
factory SuperChatData.fromJson(Map<String, dynamic> json) => SuperChatData(
|
||||||
|
list: (json['list'] as List<dynamic>?)
|
||||||
|
?.map((e) => SuperChatItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
42
lib/models_new/live/live_superchat/item.dart
Normal file
42
lib/models_new/live/live_superchat/item.dart
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/user_info.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
|
||||||
|
class SuperChatItem {
|
||||||
|
dynamic id;
|
||||||
|
dynamic uid;
|
||||||
|
int? price;
|
||||||
|
String backgroundColor;
|
||||||
|
String backgroundBottomColor;
|
||||||
|
String backgroundPriceColor;
|
||||||
|
String messageFontColor;
|
||||||
|
int endTime;
|
||||||
|
String message;
|
||||||
|
UserInfo userInfo;
|
||||||
|
bool expired = false;
|
||||||
|
|
||||||
|
SuperChatItem({
|
||||||
|
this.id,
|
||||||
|
required this.uid,
|
||||||
|
this.price,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.backgroundBottomColor,
|
||||||
|
required this.backgroundPriceColor,
|
||||||
|
required this.messageFontColor,
|
||||||
|
required this.endTime,
|
||||||
|
required this.message,
|
||||||
|
required this.userInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SuperChatItem.fromJson(Map<String, dynamic> json) => SuperChatItem(
|
||||||
|
id: json['id'] ?? Utils.generateRandomString(8),
|
||||||
|
uid: json['uid'],
|
||||||
|
price: json['price'] as int?,
|
||||||
|
backgroundColor: json['background_color'] ?? '#EDF5FF',
|
||||||
|
backgroundBottomColor: json['background_bottom_color'] ?? '#2A60B2',
|
||||||
|
backgroundPriceColor: json['background_price_color'] ?? '#7497CD',
|
||||||
|
messageFontColor: json['message_font_color'] ?? '#FFFFFF',
|
||||||
|
endTime: json['end_time'],
|
||||||
|
message: json['message'],
|
||||||
|
userInfo: UserInfo.fromJson(json['user_info'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
17
lib/models_new/live/live_superchat/user_info.dart
Normal file
17
lib/models_new/live/live_superchat/user_info.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class UserInfo {
|
||||||
|
String face;
|
||||||
|
String uname;
|
||||||
|
String nameColor;
|
||||||
|
|
||||||
|
UserInfo({
|
||||||
|
required this.face,
|
||||||
|
required this.uname,
|
||||||
|
required this.nameColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UserInfo.fromJson(Map<String, dynamic> json) => UserInfo(
|
||||||
|
face: json['face'],
|
||||||
|
uname: json['uname'],
|
||||||
|
nameColor: json['name_color'] ?? '#666666',
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart';
|
import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_mention/controller.dart';
|
import 'package:PiliPlus/pages/dynamics_mention/controller.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_mention/widgets/item.dart';
|
import 'package:PiliPlus/pages/dynamics_mention/widgets/item.dart';
|
||||||
import 'package:PiliPlus/pages/search/controller.dart' show SearchState;
|
import 'package:PiliPlus/pages/search/controller.dart' show DebounceStreamState;
|
||||||
import 'package:PiliPlus/utils/context_ext.dart';
|
import 'package:PiliPlus/utils/context_ext.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -58,7 +58,8 @@ class DynMentionPanel extends StatefulWidget {
|
|||||||
State<DynMentionPanel> createState() => _DynMentionPanelState();
|
State<DynMentionPanel> createState() => _DynMentionPanelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DynMentionPanelState extends SearchState<DynMentionPanel> {
|
class _DynMentionPanelState
|
||||||
|
extends DebounceStreamState<DynMentionPanel, String> {
|
||||||
final _controller = Get.put(DynMentionController());
|
final _controller = Get.put(DynMentionController());
|
||||||
@override
|
@override
|
||||||
Duration get duration => const Duration(milliseconds: 300);
|
Duration get duration => const Duration(milliseconds: 300);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart';
|
import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart';
|
import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart';
|
import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart';
|
||||||
import 'package:PiliPlus/pages/search/controller.dart' show SearchState;
|
import 'package:PiliPlus/pages/search/controller.dart' show DebounceStreamState;
|
||||||
import 'package:PiliPlus/utils/context_ext.dart';
|
import 'package:PiliPlus/utils/context_ext.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -56,7 +56,8 @@ class SelectTopicPanel extends StatefulWidget {
|
|||||||
State<SelectTopicPanel> createState() => _SelectTopicPanelState();
|
State<SelectTopicPanel> createState() => _SelectTopicPanelState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SelectTopicPanelState extends SearchState<SelectTopicPanel> {
|
class _SelectTopicPanelState
|
||||||
|
extends DebounceStreamState<SelectTopicPanel, String> {
|
||||||
final _controller = Get.put(SelectTopicController());
|
final _controller = Get.put(SelectTopicController());
|
||||||
@override
|
@override
|
||||||
Duration get duration => const Duration(milliseconds: 300);
|
Duration get duration => const Duration(milliseconds: 300);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:PiliPlus/models_new/live/live_dm_info/data.dart';
|
|||||||
import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
|
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart';
|
import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart';
|
||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/data_source.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/data_source.dart';
|
||||||
import 'package:PiliPlus/services/service_locator.dart';
|
import 'package:PiliPlus/services/service_locator.dart';
|
||||||
@@ -64,10 +65,14 @@ class LiveRoomController extends GetxController {
|
|||||||
LiveDmInfoData? dmInfo;
|
LiveDmInfoData? dmInfo;
|
||||||
List<RichTextItem>? savedDanmaku;
|
List<RichTextItem>? savedDanmaku;
|
||||||
RxList<DanmakuMsg> messages = <DanmakuMsg>[].obs;
|
RxList<DanmakuMsg> messages = <DanmakuMsg>[].obs;
|
||||||
|
late final Rx<SuperChatItem?> fsSC = Rx<SuperChatItem?>(null);
|
||||||
|
late final RxList<SuperChatItem> superChatMsg = <SuperChatItem>[].obs;
|
||||||
RxBool disableAutoScroll = false.obs;
|
RxBool disableAutoScroll = false.obs;
|
||||||
LiveMessageStream? _msgStream;
|
LiveMessageStream? _msgStream;
|
||||||
late final ScrollController scrollController = ScrollController()
|
late final ScrollController scrollController = ScrollController()
|
||||||
..addListener(listener);
|
..addListener(listener);
|
||||||
|
late final RxInt pageIndex = 0.obs;
|
||||||
|
PageController? pageController;
|
||||||
|
|
||||||
int? currentQn;
|
int? currentQn;
|
||||||
RxString currentQnDesc = ''.obs;
|
RxString currentQnDesc = ''.obs;
|
||||||
@@ -81,6 +86,8 @@ class LiveRoomController extends GetxController {
|
|||||||
bool? isPlaying;
|
bool? isPlaying;
|
||||||
late bool isFullScreen = false;
|
late bool isFullScreen = false;
|
||||||
|
|
||||||
|
final showSuperChat = Pref.showSuperChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@@ -92,6 +99,9 @@ class LiveRoomController extends GetxController {
|
|||||||
if (isLogin && !Pref.historyPause) {
|
if (isLogin && !Pref.historyPause) {
|
||||||
VideoHttp.roomEntryAction(roomId: roomId);
|
VideoHttp.roomEntryAction(roomId: roomId);
|
||||||
}
|
}
|
||||||
|
if (showSuperChat) {
|
||||||
|
pageController = PageController();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void>? playerInit({bool autoplay = true}) {
|
Future<void>? playerInit({bool autoplay = true}) {
|
||||||
@@ -205,27 +215,50 @@ class LiveRoomController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void jumpToBottom() {
|
||||||
|
if (scrollController.hasClients) {
|
||||||
|
scrollController.jumpTo(scrollController.position.maxScrollExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void closeLiveMsg() {
|
void closeLiveMsg() {
|
||||||
_msgStream?.close();
|
_msgStream?.close();
|
||||||
_msgStream = null;
|
_msgStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> prefetch() async {
|
||||||
|
final res = await LiveHttp.liveRoomDanmaPrefetch(roomId: roomId);
|
||||||
|
if (res['status']) {
|
||||||
|
if (res['data'] case List list) {
|
||||||
|
try {
|
||||||
|
messages.addAll(
|
||||||
|
list.cast<Map<String, dynamic>>().map(DanmakuMsg.fromPrefetch),
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(scrollToBottom);
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) debugPrint(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getSuperChatMsg() async {
|
||||||
|
final res = await LiveHttp.superChatMsg(roomId);
|
||||||
|
if (res.dataOrNull?.list case List<SuperChatItem> list) {
|
||||||
|
superChatMsg.addAll(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearSC() {
|
||||||
|
superChatMsg.removeWhere((e) => e.expired);
|
||||||
|
}
|
||||||
|
|
||||||
void startLiveMsg() {
|
void startLiveMsg() {
|
||||||
if (messages.isEmpty) {
|
if (messages.isEmpty) {
|
||||||
LiveHttp.liveRoomDanmaPrefetch(roomId: roomId).then((v) {
|
prefetch();
|
||||||
if (v['status']) {
|
if (showSuperChat) {
|
||||||
if (v['data'] case List list) {
|
getSuperChatMsg();
|
||||||
try {
|
}
|
||||||
messages.addAll(
|
|
||||||
list.cast<Map<String, dynamic>>().map(DanmakuMsg.fromPrefetch),
|
|
||||||
);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(scrollToBottom);
|
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) debugPrint(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (_msgStream != null) {
|
if (_msgStream != null) {
|
||||||
return;
|
return;
|
||||||
@@ -257,14 +290,20 @@ class LiveRoomController extends GetxController {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
|
closeLiveMsg();
|
||||||
cancelLikeTimer();
|
cancelLikeTimer();
|
||||||
cancelLiveTimer();
|
cancelLiveTimer();
|
||||||
savedDanmaku?.clear();
|
savedDanmaku?.clear();
|
||||||
savedDanmaku = null;
|
savedDanmaku = null;
|
||||||
closeLiveMsg();
|
messages.clear();
|
||||||
|
if (showSuperChat) {
|
||||||
|
superChatMsg.clear();
|
||||||
|
fsSC.value = null;
|
||||||
|
}
|
||||||
scrollController
|
scrollController
|
||||||
..removeListener(listener)
|
..removeListener(listener)
|
||||||
..dispose();
|
..dispose();
|
||||||
|
pageController?.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,49 +333,63 @@ class LiveRoomController extends GetxController {
|
|||||||
)
|
)
|
||||||
..addEventListener((obj) {
|
..addEventListener((obj) {
|
||||||
try {
|
try {
|
||||||
if (obj['cmd'] == 'DANMU_MSG') {
|
// logger.i(' 原始弹幕消息 ======> ${jsonEncode(obj)}');
|
||||||
// logger.i(' 原始弹幕消息 ======> ${jsonEncode(obj)}');
|
switch (obj['cmd']) {
|
||||||
final info = obj['info'];
|
case 'DANMU_MSG':
|
||||||
final first = info[0];
|
final info = obj['info'];
|
||||||
final content = first[15];
|
final first = info[0];
|
||||||
final extra = jsonDecode(content['extra']);
|
final content = first[15];
|
||||||
final user = content['user'];
|
final extra = jsonDecode(content['extra']);
|
||||||
final uid = user['uid'];
|
final user = content['user'];
|
||||||
messages.add(
|
final uid = user['uid'];
|
||||||
DanmakuMsg()
|
BaseEmote? uemote;
|
||||||
..name = user['base']['name']
|
if (first[13] case Map<String, dynamic> map) {
|
||||||
..uid = uid
|
uemote = BaseEmote.fromJson(map);
|
||||||
..text = info[1]
|
}
|
||||||
..emots = (extra['emots'] as Map<String, dynamic>?)?.map(
|
messages.add(
|
||||||
(k, v) => MapEntry(k, BaseEmote.fromJson(v)),
|
DanmakuMsg(
|
||||||
)
|
name: user['base']['name'],
|
||||||
..uemote = first[13] is Map<String, dynamic>
|
uid: uid,
|
||||||
? BaseEmote.fromJson(first[13])
|
text: info[1],
|
||||||
: null,
|
emots: (extra['emots'] as Map<String, dynamic>?)?.map(
|
||||||
);
|
(k, v) => MapEntry(k, BaseEmote.fromJson(v)),
|
||||||
|
),
|
||||||
if (plPlayerController.showDanmaku) {
|
uemote: uemote,
|
||||||
plPlayerController.danmakuController?.addDanmaku(
|
|
||||||
DanmakuContentItem(
|
|
||||||
extra['content'],
|
|
||||||
color: DmUtils.decimalToColor(extra['color']),
|
|
||||||
type: DmUtils.getPosition(extra['mode']),
|
|
||||||
selfSend: isLogin && uid == mid,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!disableAutoScroll.value) {
|
|
||||||
EasyThrottle.throttle(
|
if (plPlayerController.showDanmaku) {
|
||||||
'liveDm',
|
plPlayerController.danmakuController?.addDanmaku(
|
||||||
const Duration(milliseconds: 500),
|
DanmakuContentItem(
|
||||||
() => WidgetsBinding.instance.addPostFrameCallback(
|
extra['content'],
|
||||||
scrollToBottom,
|
color: DmUtils.decimalToColor(extra['color']),
|
||||||
|
type: DmUtils.getPosition(extra['mode']),
|
||||||
|
selfSend: extra['send_from_me'] ?? false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!disableAutoScroll.value) {
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'liveDm',
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
() => WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
scrollToBottom,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
case 'SUPER_CHAT_MESSAGE' when (showSuperChat):
|
||||||
|
final item = SuperChatItem.fromJson(obj['data']);
|
||||||
|
superChatMsg.insert(0, item);
|
||||||
|
if (isFullScreen) {
|
||||||
|
fsSC.value = item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (kDebugMode) debugPrint(e.toString());
|
if (kDebugMode) {
|
||||||
|
debugPrint('$e,,$obj');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
..init();
|
..init();
|
||||||
|
|||||||
149
lib/pages/live_room/superchat/superchat_card.dart
Normal file
149
lib/pages/live_room/superchat/superchat_card.dart
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
|
import 'package:PiliPlus/models/common/image_type.dart';
|
||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class SuperChatCard extends StatefulWidget {
|
||||||
|
const SuperChatCard({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
required this.onRemove,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SuperChatItem item;
|
||||||
|
final VoidCallback onRemove;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SuperChatCard> createState() => _SuperChatCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SuperChatCardState extends State<SuperChatCard> {
|
||||||
|
Timer? _timer;
|
||||||
|
RxInt _remains = 0.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.item.expired) {
|
||||||
|
_onRemove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
final offset = widget.item.endTime - now;
|
||||||
|
if (offset > 0) {
|
||||||
|
_remains = offset.obs;
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), _callback);
|
||||||
|
} else {
|
||||||
|
_onRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onRemove() {
|
||||||
|
widget
|
||||||
|
..item.expired = true
|
||||||
|
..onRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _callback(_) {
|
||||||
|
final remains = _remains.value;
|
||||||
|
if (remains > 0) {
|
||||||
|
_remains.value = remains - 1;
|
||||||
|
} else {
|
||||||
|
_cancelTimer();
|
||||||
|
_onRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cancelTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_cancelTimer();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final item = widget.item;
|
||||||
|
final bottomColor = Utils.parseColor(item.backgroundBottomColor);
|
||||||
|
final border = BorderSide(color: bottomColor);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
color: Utils.parseColor(item.backgroundColor),
|
||||||
|
border: Border(top: border, left: border, right: border),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Get.toNamed('/member?mid=${item.uid}'),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: item.userInfo.face,
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
type: ImageType.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.userInfo.uname,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Utils.parseColor(item.userInfo.nameColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"¥${item.price}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Utils.parseColor(item.backgroundPriceColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
_remains.toString(),
|
||||||
|
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
color: bottomColor,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: SelectableText(
|
||||||
|
item.message,
|
||||||
|
style: TextStyle(color: Utils.parseColor(item.messageFontColor)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
lib/pages/live_room/superchat/superchat_panel.dart
Normal file
45
lib/pages/live_room/superchat/superchat_panel.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:PiliPlus/pages/live_room/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart';
|
||||||
|
import 'package:PiliPlus/pages/search/controller.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get_state_manager/get_state_manager.dart';
|
||||||
|
|
||||||
|
class SuperChatPanel extends StatefulWidget {
|
||||||
|
const SuperChatPanel({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
final LiveRoomController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SuperChatPanel> createState() => _SuperChatPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SuperChatPanelState extends DebounceStreamState<SuperChatPanel, bool> {
|
||||||
|
@override
|
||||||
|
Duration get duration => const Duration(milliseconds: 300);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(
|
||||||
|
() => ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
physics: const ClampingScrollPhysics(),
|
||||||
|
itemCount: widget.controller.superChatMsg.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = widget.controller.superChatMsg[index];
|
||||||
|
return SuperChatCard(
|
||||||
|
key: Key(item.id.toString()),
|
||||||
|
item: item,
|
||||||
|
onRemove: () => ctr?.add(true),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onValueChanged(value) => widget.controller.clearSC();
|
||||||
|
}
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
import 'package:PiliPlus/models/common/image_type.dart';
|
import 'package:PiliPlus/models/common/image_type.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/controller.dart';
|
import 'package:PiliPlus/pages/live_room/controller.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/send_danmaku/view.dart';
|
import 'package:PiliPlus/pages/live_room/send_danmaku/view.dart';
|
||||||
|
import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart';
|
||||||
|
import 'package:PiliPlus/pages/live_room/superchat/superchat_panel.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/widgets/bottom_control.dart';
|
import 'package:PiliPlus/pages/live_room/widgets/bottom_control.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/widgets/chat.dart';
|
import 'package:PiliPlus/pages/live_room/widgets/chat_panel.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
|
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
|
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/view.dart';
|
import 'package:PiliPlus/plugin/pl_player/view.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
|
|
||||||
import 'package:PiliPlus/services/service_locator.dart';
|
import 'package:PiliPlus/services/service_locator.dart';
|
||||||
import 'package:PiliPlus/utils/duration_util.dart';
|
import 'package:PiliPlus/utils/duration_util.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
@@ -24,6 +29,7 @@ import 'package:PiliPlus/utils/utils.dart';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart' show SystemUiOverlayStyle;
|
import 'package:flutter/services.dart' show SystemUiOverlayStyle;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@@ -44,8 +50,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
late final PlPlayerController plPlayerController;
|
late final PlPlayerController plPlayerController;
|
||||||
bool get isFullScreen => plPlayerController.isFullScreen.value;
|
bool get isFullScreen => plPlayerController.isFullScreen.value;
|
||||||
|
|
||||||
final GlobalKey chatKey = GlobalKey();
|
late final GlobalKey pageKey = GlobalKey();
|
||||||
final GlobalKey playerKey = GlobalKey();
|
late final GlobalKey chatKey = GlobalKey();
|
||||||
|
late final GlobalKey scKey = GlobalKey();
|
||||||
|
late final GlobalKey playerKey = GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -193,7 +201,128 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
Alignment? alignment,
|
Alignment? alignment,
|
||||||
bool needDm = true,
|
bool needDm = true,
|
||||||
}) {
|
}) {
|
||||||
|
if (!isFullScreen) {
|
||||||
|
_liveRoomController.fsSC.value = null;
|
||||||
|
}
|
||||||
_liveRoomController.isFullScreen = isFullScreen;
|
_liveRoomController.isFullScreen = isFullScreen;
|
||||||
|
Widget player = Obx(() {
|
||||||
|
if (_liveRoomController.isLoaded.value) {
|
||||||
|
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
|
||||||
|
return PLVideoPlayer(
|
||||||
|
key: playerKey,
|
||||||
|
maxWidth: width,
|
||||||
|
maxHeight: height,
|
||||||
|
fill: fill,
|
||||||
|
alignment: alignment,
|
||||||
|
plPlayerController: plPlayerController,
|
||||||
|
headerControl: LiveHeaderControl(
|
||||||
|
title: roomInfoH5?.roomInfo?.title,
|
||||||
|
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
|
||||||
|
plPlayerController: plPlayerController,
|
||||||
|
onSendDanmaku: onSendDanmaku,
|
||||||
|
onPlayAudio: _liveRoomController.queryLiveUrl,
|
||||||
|
),
|
||||||
|
bottomControl: BottomControl(
|
||||||
|
plPlayerController: plPlayerController,
|
||||||
|
liveRoomCtr: _liveRoomController,
|
||||||
|
onRefresh: _liveRoomController.queryLiveUrl,
|
||||||
|
),
|
||||||
|
danmuWidget: !needDm
|
||||||
|
? null
|
||||||
|
: LiveDanmaku(
|
||||||
|
liveRoomController: _liveRoomController,
|
||||||
|
plPlayerController: plPlayerController,
|
||||||
|
isFullScreen: isFullScreen,
|
||||||
|
isPipMode: isPipMode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
});
|
||||||
|
if (isFullScreen && _liveRoomController.showSuperChat) {
|
||||||
|
player = Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: player),
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
Positioned(
|
||||||
|
top: 50,
|
||||||
|
right: 0,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_liveRoomController.fsSC.value = SuperChatItem.fromJson({
|
||||||
|
"id": Utils.generateRandomString(8),
|
||||||
|
"price": 66,
|
||||||
|
"end_time":
|
||||||
|
DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5,
|
||||||
|
"message": Utils.generateRandomString(55),
|
||||||
|
"user_info": {
|
||||||
|
"face": "",
|
||||||
|
"uname": Utils.generateRandomString(8),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('add superchat'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 90,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_liveRoomController.fsSC.value = null;
|
||||||
|
},
|
||||||
|
child: const Text('remove superchat'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Positioned(
|
||||||
|
left: padding.left + 25,
|
||||||
|
bottom: 25,
|
||||||
|
child: Obx(() {
|
||||||
|
final item = _liveRoomController.fsSC.value;
|
||||||
|
if (item == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return SizedBox(
|
||||||
|
key: Key(item.id.toString()),
|
||||||
|
width: 255,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6, top: 6),
|
||||||
|
child: SuperChatCard(
|
||||||
|
item: item,
|
||||||
|
onRemove: () => _liveRoomController.fsSC.value = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: iconButton(
|
||||||
|
size: 24,
|
||||||
|
iconSize: 14,
|
||||||
|
context: context,
|
||||||
|
bgColor: const Color(0xEEFFFFFF),
|
||||||
|
iconColor: Colors.black54,
|
||||||
|
icon: Icons.clear,
|
||||||
|
onPressed: () =>
|
||||||
|
_liveRoomController.fsSC.value = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: !isFullScreen,
|
canPop: !isFullScreen,
|
||||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||||
@@ -201,40 +330,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
plPlayerController.triggerFullScreen(status: false);
|
plPlayerController.triggerFullScreen(status: false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Obx(() {
|
child: player,
|
||||||
if (_liveRoomController.isLoaded.value) {
|
|
||||||
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
|
|
||||||
return PLVideoPlayer(
|
|
||||||
key: playerKey,
|
|
||||||
maxWidth: width,
|
|
||||||
maxHeight: height,
|
|
||||||
fill: fill,
|
|
||||||
alignment: alignment,
|
|
||||||
plPlayerController: plPlayerController,
|
|
||||||
headerControl: LiveHeaderControl(
|
|
||||||
title: roomInfoH5?.roomInfo?.title,
|
|
||||||
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
|
|
||||||
plPlayerController: plPlayerController,
|
|
||||||
onSendDanmaku: onSendDanmaku,
|
|
||||||
onPlayAudio: _liveRoomController.queryLiveUrl,
|
|
||||||
),
|
|
||||||
bottomControl: BottomControl(
|
|
||||||
plPlayerController: plPlayerController,
|
|
||||||
liveRoomCtr: _liveRoomController,
|
|
||||||
onRefresh: _liveRoomController.queryLiveUrl,
|
|
||||||
),
|
|
||||||
danmuWidget: !needDm
|
|
||||||
? null
|
|
||||||
: LiveDanmaku(
|
|
||||||
liveRoomController: _liveRoomController,
|
|
||||||
plPlayerController: plPlayerController,
|
|
||||||
isFullScreen: isFullScreen,
|
|
||||||
isPipMode: isPipMode,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +440,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
|
|
||||||
Widget get _buildPP {
|
Widget get _buildPP {
|
||||||
final isFullScreen = this.isFullScreen;
|
final isFullScreen = this.isFullScreen;
|
||||||
final bottomHeight = 80.0 + padding.bottom;
|
final bottomHeight = 70 + padding.bottom;
|
||||||
final topPadding = padding.top + kToolbarHeight;
|
final topPadding = padding.top + kToolbarHeight;
|
||||||
final videoHeight = maxHeight - bottomHeight - topPadding;
|
final videoHeight = maxHeight - bottomHeight - topPadding;
|
||||||
return Stack(
|
return Stack(
|
||||||
@@ -562,7 +658,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
|
|
||||||
Widget get _buildBodyH {
|
Widget get _buildBodyH {
|
||||||
final videoWidth =
|
final videoWidth =
|
||||||
clampDouble(maxHeight / maxWidth * 1.08, 0.58, 0.75) * maxWidth;
|
clampDouble(maxHeight / maxWidth * 1.08, 0.56, 0.7) * maxWidth;
|
||||||
final videoHeight = maxHeight - padding.top;
|
final videoHeight = maxHeight - padding.top;
|
||||||
return Obx(
|
return Obx(
|
||||||
() {
|
() {
|
||||||
@@ -619,148 +715,215 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildChatWidget([bool isPP = false]) => Padding(
|
Widget _buildChatWidget([bool isPP = false]) {
|
||||||
padding: EdgeInsets.only(bottom: 16, top: !isPortrait ? 0 : 16),
|
Widget chat() => LiveRoomChatPanel(
|
||||||
child: LiveRoomChat(
|
|
||||||
key: chatKey,
|
key: chatKey,
|
||||||
isPP: isPP,
|
isPP: isPP,
|
||||||
roomId: _liveRoomController.roomId,
|
roomId: _liveRoomController.roomId,
|
||||||
liveRoomController: _liveRoomController,
|
liveRoomController: _liveRoomController,
|
||||||
),
|
);
|
||||||
);
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 12, top: !isPortrait ? 0 : 12),
|
||||||
|
child: _liveRoomController.showSuperChat
|
||||||
|
? PageView(
|
||||||
|
key: pageKey,
|
||||||
|
controller: _liveRoomController.pageController,
|
||||||
|
physics: const CustomTabBarViewClampingScrollPhysics(),
|
||||||
|
onPageChanged: (value) =>
|
||||||
|
_liveRoomController.pageIndex.value = value,
|
||||||
|
children: [
|
||||||
|
KeepAliveWrapper(builder: (context) => chat()),
|
||||||
|
KeepAliveWrapper(
|
||||||
|
builder: (context) => SuperChatPanel(
|
||||||
|
key: scKey,
|
||||||
|
controller: _liveRoomController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: chat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget get _buildInputWidget => Container(
|
Widget get _buildInputWidget {
|
||||||
padding: EdgeInsets.only(
|
final child = Container(
|
||||||
top: 5,
|
padding: EdgeInsets.only(
|
||||||
left: 10,
|
top: 5,
|
||||||
right: 10,
|
left: 10,
|
||||||
bottom: 15 + padding.bottom,
|
right: 10,
|
||||||
),
|
bottom: padding.bottom,
|
||||||
height: 80 + padding.bottom,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(20),
|
|
||||||
topRight: Radius.circular(20),
|
|
||||||
),
|
),
|
||||||
border: Border(
|
height: 70 + padding.bottom,
|
||||||
top: BorderSide(color: Color(0x1AFFFFFF)),
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
border: Border(top: BorderSide(color: Color(0x1AFFFFFF))),
|
||||||
|
color: Color(0x1AFFFFFF),
|
||||||
),
|
),
|
||||||
color: Color(0x1AFFFFFF),
|
child: GestureDetector(
|
||||||
),
|
onTap: onSendDanmaku,
|
||||||
child: GestureDetector(
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: onSendDanmaku,
|
child: Padding(
|
||||||
behavior: HitTestBehavior.opaque,
|
padding: const EdgeInsets.only(top: 5, bottom: 10),
|
||||||
child: Padding(
|
child: Align(
|
||||||
padding: const EdgeInsets.only(top: 5, bottom: 10),
|
alignment: Alignment.topCenter,
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
Obx(
|
||||||
() {
|
() {
|
||||||
final enableShowDanmaku =
|
final enableShowDanmaku =
|
||||||
plPlayerController.enableShowDanmaku.value;
|
plPlayerController.enableShowDanmaku.value;
|
||||||
return ComBtn(
|
return SizedBox(
|
||||||
onTap: () {
|
width: 34,
|
||||||
final newVal = !enableShowDanmaku;
|
height: 34,
|
||||||
plPlayerController.enableShowDanmaku.value = newVal;
|
child: IconButton(
|
||||||
if (!plPlayerController.tempPlayerConf) {
|
style: IconButton.styleFrom(
|
||||||
GStorage.setting.put(
|
padding: EdgeInsets.zero,
|
||||||
SettingBoxKey.enableShowDanmaku,
|
|
||||||
newVal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: enableShowDanmaku
|
|
||||||
? const Icon(
|
|
||||||
size: 22,
|
|
||||||
Icons.subtitles_outlined,
|
|
||||||
color: Color(0xFFEEEEEE),
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
size: 22,
|
|
||||||
Icons.subtitles_off_outlined,
|
|
||||||
color: Color(0xFFEEEEEE),
|
|
||||||
),
|
),
|
||||||
);
|
onPressed: () {
|
||||||
},
|
final newVal = !enableShowDanmaku;
|
||||||
),
|
plPlayerController.enableShowDanmaku.value = newVal;
|
||||||
const Expanded(
|
if (!plPlayerController.tempPlayerConf) {
|
||||||
child: Text(
|
GStorage.setting.put(
|
||||||
'发送弹幕',
|
SettingBoxKey.enableShowDanmaku,
|
||||||
style: TextStyle(color: Color(0xFFEEEEEE)),
|
newVal,
|
||||||
),
|
);
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final theme = Theme.of(context).colorScheme;
|
|
||||||
return Material(
|
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
InkWell(
|
|
||||||
overlayColor: overlayColor(theme),
|
|
||||||
customBorder: const CircleBorder(),
|
|
||||||
onTapDown: _liveRoomController.onLikeTapDown,
|
|
||||||
onTapUp: _liveRoomController.onLikeTapUp,
|
|
||||||
onTapCancel: _liveRoomController.onLikeTapUp,
|
|
||||||
child: const SizedBox.square(
|
|
||||||
dimension: 34,
|
|
||||||
child: Icon(
|
|
||||||
size: 22,
|
|
||||||
color: Color(0xFFEEEEEE),
|
|
||||||
Icons.thumb_up_off_alt,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: -12,
|
|
||||||
top: -12,
|
|
||||||
child: Obx(() {
|
|
||||||
final likeClickTime =
|
|
||||||
_liveRoomController.likeClickTime.value;
|
|
||||||
if (likeClickTime == 0) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
}
|
||||||
return AnimatedSwitcher(
|
},
|
||||||
duration: const Duration(milliseconds: 160),
|
icon: enableShowDanmaku
|
||||||
transitionBuilder: (child, animation) {
|
? const Icon(
|
||||||
return ScaleTransition(
|
size: 22,
|
||||||
scale: animation,
|
Icons.subtitles_outlined,
|
||||||
child: child,
|
color: Color(0xFFEEEEEE),
|
||||||
);
|
)
|
||||||
},
|
: const Icon(
|
||||||
child: Text(
|
size: 22,
|
||||||
key: ValueKey(likeClickTime),
|
Icons.subtitles_off_outlined,
|
||||||
'x$likeClickTime',
|
color: Color(0xFFEEEEEE),
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 16,
|
),
|
||||||
color: theme.brightness.isDark
|
);
|
||||||
? theme.primary
|
},
|
||||||
: theme.inversePrimary,
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'发送弹幕',
|
||||||
|
style: TextStyle(color: Color(0xFFEEEEEE)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final theme = Theme.of(context).colorScheme;
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
overlayColor: overlayColor(theme),
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
onTapDown: _liveRoomController.onLikeTapDown,
|
||||||
|
onTapUp: _liveRoomController.onLikeTapUp,
|
||||||
|
onTapCancel: _liveRoomController.onLikeTapUp,
|
||||||
|
child: const SizedBox.square(
|
||||||
|
dimension: 34,
|
||||||
|
child: Icon(
|
||||||
|
size: 22,
|
||||||
|
color: Color(0xFFEEEEEE),
|
||||||
|
Icons.thumb_up_off_alt,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
Positioned(
|
||||||
|
right: -12,
|
||||||
|
top: -12,
|
||||||
|
child: Obx(() {
|
||||||
|
final likeClickTime =
|
||||||
|
_liveRoomController.likeClickTime.value;
|
||||||
|
if (likeClickTime == 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 160),
|
||||||
|
transitionBuilder: (child, animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
key: ValueKey(likeClickTime),
|
||||||
|
'x$likeClickTime',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.brightness.isDark
|
||||||
|
? theme.primary
|
||||||
|
: theme.inversePrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: IconButton(
|
||||||
|
style: IconButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
|
onPressed: () => onSendDanmaku(true),
|
||||||
|
icon: const Icon(
|
||||||
|
size: 22,
|
||||||
|
color: Color(0xFFEEEEEE),
|
||||||
|
Icons.emoji_emotions_outlined,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
ComBtn(
|
),
|
||||||
onTap: () => onSendDanmaku(true),
|
|
||||||
icon: const Icon(
|
|
||||||
size: 22,
|
|
||||||
color: Color(0xFFEEEEEE),
|
|
||||||
Icons.emoji_emotions_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
if (_liveRoomController.showSuperChat) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Obx(() {
|
||||||
|
return ClipRect(
|
||||||
|
clipper: _BorderClipper(
|
||||||
|
_liveRoomController.pageIndex.value == 0,
|
||||||
|
),
|
||||||
|
child: const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Colors.white38),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(width: double.infinity, height: 20),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
WidgetStateProperty<Color?>? overlayColor(ColorScheme theme) =>
|
WidgetStateProperty<Color?>? overlayColor(ColorScheme theme) =>
|
||||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||||
@@ -828,6 +991,27 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BorderClipper extends CustomClipper<Rect> {
|
||||||
|
_BorderClipper(this.isLeft);
|
||||||
|
|
||||||
|
final bool isLeft;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Rect getClip(Size size) {
|
||||||
|
return Rect.fromLTWH(
|
||||||
|
isLeft ? 0 : size.width / 2,
|
||||||
|
0,
|
||||||
|
size.width / 2,
|
||||||
|
size.height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReclip(_BorderClipper oldClipper) {
|
||||||
|
return isLeft != oldClipper.isLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LiveDanmaku extends StatefulWidget {
|
class LiveDanmaku extends StatefulWidget {
|
||||||
final LiveRoomController liveRoomController;
|
final LiveRoomController liveRoomController;
|
||||||
final PlPlayerController plPlayerController;
|
final PlPlayerController plPlayerController;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/models/common/image_type.dart';
|
import 'package:PiliPlus/models/common/image_type.dart';
|
||||||
import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart';
|
import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart';
|
||||||
|
import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
|
||||||
import 'package:PiliPlus/pages/live_room/controller.dart';
|
import 'package:PiliPlus/pages/live_room/controller.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class LiveRoomChat extends StatelessWidget {
|
class LiveRoomChatPanel extends StatelessWidget {
|
||||||
const LiveRoomChat({
|
const LiveRoomChatPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.roomId,
|
required this.roomId,
|
||||||
required this.liveRoomController,
|
required this.liveRoomController,
|
||||||
@@ -33,24 +35,23 @@ class LiveRoomChat extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Obx(
|
Obx(
|
||||||
() => ListView.separated(
|
() => ListView.separated(
|
||||||
padding: EdgeInsets.zero,
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
controller: liveRoomController.scrollController,
|
controller: liveRoomController.scrollController,
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 6),
|
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||||
itemCount: liveRoomController.messages.length,
|
itemCount: liveRoomController.messages.length,
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = liveRoomController.messages[index];
|
final item = liveRoomController.messages[index];
|
||||||
return Container(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 10,
|
horizontal: 10,
|
||||||
vertical: 5,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bg,
|
color: bg,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(18)),
|
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||||
),
|
),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
@@ -61,14 +62,11 @@ class LiveRoomChat extends StatelessWidget {
|
|||||||
color: nameColor,
|
color: nameColor,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: item.uid == 0
|
||||||
..onTap = () {
|
? null
|
||||||
try {
|
: (TapGestureRecognizer()
|
||||||
Get.toNamed('/member?mid=${item.uid}');
|
..onTap = () =>
|
||||||
} catch (err) {
|
Get.toNamed('/member?mid=${item.uid}')),
|
||||||
if (kDebugMode) debugPrint(err.toString());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
_buildMsg(devicePixelRatio, item),
|
_buildMsg(devicePixelRatio, item),
|
||||||
],
|
],
|
||||||
@@ -79,12 +77,101 @@ class LiveRoomChat extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (kDebugMode && liveRoomController.showSuperChat) ...[
|
||||||
|
Positioned(
|
||||||
|
top: 50,
|
||||||
|
right: 0,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
liveRoomController.superChatMsg.insert(
|
||||||
|
0,
|
||||||
|
SuperChatItem.fromJson({
|
||||||
|
"id": Utils.generateRandomString(8),
|
||||||
|
"price": 66,
|
||||||
|
"end_time":
|
||||||
|
DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5,
|
||||||
|
"message": "message message message message message",
|
||||||
|
"user_info": {
|
||||||
|
"face": "",
|
||||||
|
"uname": "UNAME",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('add superchat'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 90,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (liveRoomController.superChatMsg.isNotEmpty) {
|
||||||
|
liveRoomController.superChatMsg.removeLast();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('remove superchat'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (liveRoomController.showSuperChat)
|
||||||
|
Positioned(
|
||||||
|
top: 12,
|
||||||
|
right: 12,
|
||||||
|
child: Obx(() {
|
||||||
|
final isEmpty = liveRoomController.superChatMsg.isEmpty;
|
||||||
|
return AnimatedOpacity(
|
||||||
|
opacity: isEmpty ? 0 : 1,
|
||||||
|
duration: const Duration(milliseconds: 120),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: isEmpty
|
||||||
|
? null
|
||||||
|
: () => liveRoomController.pageController?.animateToPage(
|
||||||
|
1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
color: const Color(0x2FFFFFFF),
|
||||||
|
border: Border.all(color: Colors.white24, width: 0.7),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
|
||||||
|
child: Text.rich(
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
strutStyle: const StrutStyle(height: 1, leading: 0),
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
'SC: ${liveRoomController.superChatMsg.length}',
|
||||||
|
),
|
||||||
|
const WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Icon(
|
||||||
|
size: 18,
|
||||||
|
Icons.keyboard_arrow_right,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => liveRoomController.disableAutoScroll.value
|
() => liveRoomController.disableAutoScroll.value
|
||||||
? Positioned(
|
? Positioned(
|
||||||
right: 12,
|
right: 12,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
visualDensity: VisualDensity.comfortable,
|
||||||
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.arrow_downward_rounded,
|
Icons.arrow_downward_rounded,
|
||||||
size: 20,
|
size: 20,
|
||||||
@@ -92,7 +179,7 @@ class LiveRoomChat extends StatelessWidget {
|
|||||||
label: const Text('回到底部'),
|
label: const Text('回到底部'),
|
||||||
onPressed: () => liveRoomController
|
onPressed: () => liveRoomController
|
||||||
..disableAutoScroll.value = false
|
..disableAutoScroll.value = false
|
||||||
..scrollToBottom(),
|
..jumpToBottom(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
@@ -474,15 +474,13 @@ class UserInfoCard extends StatelessWidget {
|
|||||||
bool isLight,
|
bool isLight,
|
||||||
SpacePrInfo prInfo,
|
SpacePrInfo prInfo,
|
||||||
) {
|
) {
|
||||||
final textColor = !isLight
|
final textColor = Utils.parseColor(
|
||||||
? Color(int.parse('FF${prInfo.textColorNight.substring(1)}', radix: 16))
|
isLight ? prInfo.textColor : prInfo.textColorNight,
|
||||||
: Color(int.parse('FF${prInfo.textColor.substring(1)}', radix: 16));
|
);
|
||||||
Widget child = Container(
|
Widget child = Container(
|
||||||
margin: const EdgeInsets.only(top: 8),
|
margin: const EdgeInsets.only(top: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
color: !isLight
|
color: Utils.parseColor(isLight ? prInfo.bgColor : prInfo.bgColorNight),
|
||||||
? Color(int.parse('FF${prInfo.bgColorNight.substring(1)}', radix: 16))
|
|
||||||
: Color(int.parse('FF${prInfo.bgColor.substring(1)}', radix: 16)),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (!isLight && prInfo.iconNight?.isNotEmpty == true) ...[
|
if (!isLight && prInfo.iconNight?.isNotEmpty == true) ...[
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ mixin DebounceStreamMixin<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SearchState<T extends StatefulWidget> extends State<T>
|
abstract class DebounceStreamState<T extends StatefulWidget, S> extends State<T>
|
||||||
with DebounceStreamMixin<String> {
|
with DebounceStreamMixin<S> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
subDispose();
|
subDispose();
|
||||||
|
|||||||
@@ -129,6 +129,13 @@ List<SettingsModel> get playSettings => [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SettingsModel(
|
||||||
|
settingsType: SettingsType.sw1tch,
|
||||||
|
title: '显示 SuperChat',
|
||||||
|
leading: Icon(Icons.live_tv),
|
||||||
|
setKey: SettingBoxKey.showSuperChat,
|
||||||
|
defaultVal: true,
|
||||||
|
),
|
||||||
const SettingsModel(
|
const SettingsModel(
|
||||||
settingsType: SettingsType.sw1tch,
|
settingsType: SettingsType.sw1tch,
|
||||||
title: '竖屏扩大展示',
|
title: '竖屏扩大展示',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
|
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
|
||||||
import 'package:PiliPlus/pages/search/controller.dart' show SearchState;
|
import 'package:PiliPlus/pages/search/controller.dart' show DebounceStreamState;
|
||||||
import 'package:PiliPlus/pages/setting/models/extra_settings.dart';
|
import 'package:PiliPlus/pages/setting/models/extra_settings.dart';
|
||||||
import 'package:PiliPlus/pages/setting/models/model.dart';
|
import 'package:PiliPlus/pages/setting/models/model.dart';
|
||||||
import 'package:PiliPlus/pages/setting/models/play_settings.dart';
|
import 'package:PiliPlus/pages/setting/models/play_settings.dart';
|
||||||
@@ -22,7 +22,8 @@ class SettingsSearchPage extends StatefulWidget {
|
|||||||
State<SettingsSearchPage> createState() => _SettingsSearchPageState();
|
State<SettingsSearchPage> createState() => _SettingsSearchPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsSearchPageState extends SearchState<SettingsSearchPage> {
|
class _SettingsSearchPageState
|
||||||
|
extends DebounceStreamState<SettingsSearchPage, String> {
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
final RxList<SettingsModel> _list = <SettingsModel>[].obs;
|
final RxList<SettingsModel> _list = <SettingsModel>[].obs;
|
||||||
late final _settings = [
|
late final _settings = [
|
||||||
@@ -91,19 +92,24 @@ class _SettingsSearchPageState extends SearchState<SettingsSearchPage> {
|
|||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
ViewSliverSafeArea(
|
ViewSliverSafeArea(
|
||||||
sliver: Obx(
|
sliver: MediaQuery.removeViewPadding(
|
||||||
() => _list.isEmpty
|
context: context,
|
||||||
? const HttpError()
|
removeLeft: true,
|
||||||
: SliverWaterfallFlow(
|
removeRight: true,
|
||||||
gridDelegate:
|
child: Obx(
|
||||||
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
() => _list.isEmpty
|
||||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
? const HttpError()
|
||||||
),
|
: SliverWaterfallFlow(
|
||||||
delegate: SliverChildBuilderDelegate(
|
gridDelegate:
|
||||||
(_, index) => _list[index].widget,
|
SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||||
childCount: _list.length,
|
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||||
|
),
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(_, index) => _list[index].widget,
|
||||||
|
childCount: _list.length,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class SettingBoxKey {
|
|||||||
enableAutoBrightness = 'enableAutoBrightness',
|
enableAutoBrightness = 'enableAutoBrightness',
|
||||||
enableAutoEnter = 'enableAutoEnter',
|
enableAutoEnter = 'enableAutoEnter',
|
||||||
enableAutoExit = 'enableAutoExit',
|
enableAutoExit = 'enableAutoExit',
|
||||||
enableOnlineTotal = 'enableOnlineTotal';
|
enableOnlineTotal = 'enableOnlineTotal',
|
||||||
|
showSuperChat = 'showSuperChat';
|
||||||
|
|
||||||
static const String enableVerticalExpand = 'enableVerticalExpand',
|
static const String enableVerticalExpand = 'enableVerticalExpand',
|
||||||
feedBackEnable = 'feedBackEnable',
|
feedBackEnable = 'feedBackEnable',
|
||||||
|
|||||||
@@ -807,4 +807,7 @@ class Pref {
|
|||||||
|
|
||||||
static bool get showMemberShop =>
|
static bool get showMemberShop =>
|
||||||
_setting.get(SettingBoxKey.showMemberShop, defaultValue: false);
|
_setting.get(SettingBoxKey.showMemberShop, defaultValue: false);
|
||||||
|
|
||||||
|
static bool get showSuperChat =>
|
||||||
|
_setting.get(SettingBoxKey.showSuperChat, defaultValue: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class Utils {
|
|||||||
|
|
||||||
static const channel = MethodChannel("PiliPlus");
|
static const channel = MethodChannel("PiliPlus");
|
||||||
|
|
||||||
|
static Color parseColor(String color) =>
|
||||||
|
Color(int.parse(color.replaceFirst('#', 'FF'), radix: 16));
|
||||||
|
|
||||||
static int? _sdkInt;
|
static int? _sdkInt;
|
||||||
|
|
||||||
static Future<int> get sdkInt async {
|
static Future<int> get sdkInt async {
|
||||||
|
|||||||
Reference in New Issue
Block a user