mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
@@ -81,13 +81,14 @@ Future<void> autoWrapReportDialog(
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14, top: 6),
|
||||
child: CheckBoxText(
|
||||
text: '拉黑该用户',
|
||||
onChanged: (value) => banUid = value,
|
||||
if (options != ReportOptions.liveDanmakuReport)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14, top: 6),
|
||||
child: CheckBoxText(
|
||||
text: '拉黑该用户',
|
||||
onChanged: (value) => banUid = value,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
|
||||
157
lib/common/widgets/flutter/popup_menu.dart
Normal file
157
lib/common/widgets/flutter/popup_menu.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomPopupMenuItem<T> extends PopupMenuEntry<T> {
|
||||
const CustomPopupMenuItem({
|
||||
super.key,
|
||||
this.value,
|
||||
this.height = kMinInteractiveDimension,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final T? value;
|
||||
|
||||
@override
|
||||
final double height;
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
bool represents(T? value) => value == this.value;
|
||||
|
||||
@override
|
||||
CustomPopupMenuItemState<T, CustomPopupMenuItem<T>> createState() =>
|
||||
CustomPopupMenuItemState<T, CustomPopupMenuItem<T>>();
|
||||
}
|
||||
|
||||
class CustomPopupMenuItemState<T, W extends CustomPopupMenuItem<T>>
|
||||
extends State<W> {
|
||||
@protected
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
const Set<WidgetState> states = <WidgetState>{};
|
||||
|
||||
final style =
|
||||
popupMenuTheme.labelTextStyle?.resolve(states)! ??
|
||||
_PopupMenuDefaultsM3(context).labelTextStyle!.resolve(states)!;
|
||||
|
||||
return ListTileTheme.merge(
|
||||
contentPadding: .zero,
|
||||
titleTextStyle: style,
|
||||
child: AnimatedDefaultTextStyle(
|
||||
style: style,
|
||||
duration: kThemeChangeDuration,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: widget.height),
|
||||
child: Padding(
|
||||
padding: _PopupMenuDefaultsM3.menuItemPadding,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomPopupMenuDivider extends PopupMenuEntry<Never> {
|
||||
const CustomPopupMenuDivider({
|
||||
super.key,
|
||||
required this.height,
|
||||
this.thickness,
|
||||
this.indent,
|
||||
this.endIndent,
|
||||
this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
final double height;
|
||||
|
||||
final double? thickness;
|
||||
|
||||
final double? indent;
|
||||
|
||||
final double? endIndent;
|
||||
|
||||
final BorderRadiusGeometry? radius;
|
||||
|
||||
@override
|
||||
bool represents(void value) => false;
|
||||
|
||||
@override
|
||||
State<CustomPopupMenuDivider> createState() => _CustomPopupMenuDividerState();
|
||||
}
|
||||
|
||||
class _CustomPopupMenuDividerState extends State<CustomPopupMenuDivider> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Divider(
|
||||
height: widget.height,
|
||||
thickness: widget.thickness,
|
||||
indent: widget.indent,
|
||||
color: ColorScheme.of(context).outline.withValues(alpha: 0.2),
|
||||
endIndent: widget.endIndent,
|
||||
radius: widget.radius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
|
||||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||
// "END GENERATED" comments are generated from data in the Material
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
// dart format off
|
||||
class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
|
||||
_PopupMenuDefaultsM3(this.context)
|
||||
: super(elevation: 3.0);
|
||||
|
||||
final BuildContext context;
|
||||
late final ThemeData _theme = Theme.of(context);
|
||||
late final ColorScheme _colors = _theme.colorScheme;
|
||||
late final TextTheme _textTheme = _theme.textTheme;
|
||||
|
||||
@override WidgetStateProperty<TextStyle?>? get labelTextStyle {
|
||||
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
// TODO(quncheng): Update this hard-coded value to use the latest tokens.
|
||||
final TextStyle style = _textTheme.labelLarge!;
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return style.apply(color: _colors.onSurface.withValues(alpha: 0.38));
|
||||
}
|
||||
return style.apply(color: _colors.onSurface);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Color? get color => _colors.surfaceContainer;
|
||||
|
||||
@override
|
||||
Color? get shadowColor => _colors.shadow;
|
||||
|
||||
@override
|
||||
Color? get surfaceTintColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
ShapeBorder? get shape => const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
|
||||
|
||||
// TODO(bleroux): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
@override
|
||||
EdgeInsets? get menuPadding => const EdgeInsets.symmetric(vertical: 8.0);
|
||||
|
||||
// TODO(tahatesser): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
static EdgeInsets menuItemPadding = const EdgeInsets.symmetric(horizontal: 12.0);
|
||||
}// dart format on
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
@@ -981,4 +981,7 @@ abstract final class Api {
|
||||
|
||||
static const String liveContributionRank =
|
||||
'${HttpString.liveBaseUrl}/xlive/general-interface/v1/rank/queryContributionRank';
|
||||
|
||||
static const String superChatReport =
|
||||
'${HttpString.liveBaseUrl}/av/v1/SuperChat/report';
|
||||
}
|
||||
|
||||
@@ -695,4 +695,40 @@ abstract final class LiveHttp {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> superChatReport({
|
||||
required int id,
|
||||
required Object roomId,
|
||||
required Object uid,
|
||||
required String msg,
|
||||
required String reason,
|
||||
required int ts,
|
||||
required String token,
|
||||
}) async {
|
||||
final csrf = Accounts.main.csrf;
|
||||
final res = await Request().post(
|
||||
Api.superChatReport,
|
||||
data: {
|
||||
'id': id,
|
||||
'roomid': roomId,
|
||||
'uid': uid,
|
||||
'msg': msg,
|
||||
'reason': reason,
|
||||
'ts': ts,
|
||||
'sign': '',
|
||||
'reason_id': reason,
|
||||
'token': token,
|
||||
'id_str': id.toString(),
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
'visit_id': '',
|
||||
},
|
||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return const Success(null);
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class BiliDownloadEntryInfo with MultiSelectData {
|
||||
),
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(
|
||||
height: 35,
|
||||
height: 38,
|
||||
child: const Text(
|
||||
'查看详情页',
|
||||
style: TextStyle(fontSize: 13),
|
||||
@@ -99,7 +99,7 @@ class BiliDownloadEntryInfo with MultiSelectData {
|
||||
),
|
||||
if (ownerId case final mid?)
|
||||
PopupMenuItem(
|
||||
height: 35,
|
||||
height: 38,
|
||||
child: Text(
|
||||
'访问${ownerName != null ? ':$ownerName' : '用户主页'}',
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -3,40 +3,60 @@ import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class SuperChatItem {
|
||||
int id;
|
||||
int? uid;
|
||||
int? price;
|
||||
int uid;
|
||||
int price;
|
||||
String backgroundColor;
|
||||
String backgroundBottomColor;
|
||||
String backgroundPriceColor;
|
||||
String messageFontColor;
|
||||
int endTime;
|
||||
String message;
|
||||
String token;
|
||||
int ts;
|
||||
UserInfo userInfo;
|
||||
bool expired = false;
|
||||
|
||||
SuperChatItem({
|
||||
required this.id,
|
||||
required this.uid,
|
||||
this.price,
|
||||
required this.price,
|
||||
required this.backgroundColor,
|
||||
required this.backgroundBottomColor,
|
||||
required this.backgroundPriceColor,
|
||||
required this.messageFontColor,
|
||||
required this.endTime,
|
||||
required this.message,
|
||||
required this.token,
|
||||
required this.ts,
|
||||
required this.userInfo,
|
||||
});
|
||||
|
||||
static SuperChatItem get random => SuperChatItem.fromJson({
|
||||
"id": Utils.random.nextInt(2147483647),
|
||||
"uid": 0,
|
||||
"price": 66,
|
||||
"end_time": DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5,
|
||||
"message": Utils.generateRandomString(55),
|
||||
"user_info": {
|
||||
"face": "",
|
||||
"uname": "UNAME",
|
||||
},
|
||||
'token': '',
|
||||
'ts': 0,
|
||||
});
|
||||
|
||||
factory SuperChatItem.fromJson(Map<String, dynamic> json) => SuperChatItem(
|
||||
id: json['id'] ?? Utils.random.nextInt(2147483647),
|
||||
uid: json['uid'],
|
||||
price: json['price'] as int?,
|
||||
id: Utils.safeToInt(json['id']) ?? Utils.random.nextInt(2147483647),
|
||||
uid: Utils.safeToInt(json['uid'])!,
|
||||
price: json['price'],
|
||||
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'],
|
||||
endTime: Utils.safeToInt(json['end_time'])!,
|
||||
message: json['message'],
|
||||
token: json['token'],
|
||||
ts: Utils.safeToInt(json['ts'])!,
|
||||
userInfo: UserInfo.fromJson(json['user_info'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
@@ -50,6 +70,8 @@ class SuperChatItem {
|
||||
String? messageFontColor,
|
||||
int? endTime,
|
||||
String? message,
|
||||
String? token,
|
||||
int? ts,
|
||||
UserInfo? userInfo,
|
||||
bool? expired,
|
||||
}) {
|
||||
@@ -64,6 +86,8 @@ class SuperChatItem {
|
||||
messageFontColor: messageFontColor ?? this.messageFontColor,
|
||||
endTime: endTime ?? this.endTime,
|
||||
message: message ?? this.message,
|
||||
token: token ?? this.token,
|
||||
ts: ts ?? this.ts,
|
||||
userInfo: userInfo ?? this.userInfo,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
}
|
||||
|
||||
late double _mentionOffset = 0;
|
||||
Future<void> onMention([bool fromClick = false]) async {
|
||||
Future<void>? onMention([bool fromClick = false]) async {
|
||||
controller.keepChatPanel();
|
||||
final res = await DynMentionPanel.onDynMention(
|
||||
context,
|
||||
|
||||
@@ -157,56 +157,39 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
onPressed: () => Get.toNamed('/historySearch'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (String type) {
|
||||
switch (type) {
|
||||
case 'pause':
|
||||
_historyController.baseCtr.onPauseHistory(
|
||||
context,
|
||||
);
|
||||
break;
|
||||
case 'clear':
|
||||
_historyController.baseCtr.onClearHistory(
|
||||
context,
|
||||
() {
|
||||
_historyController.loadingState.value = const Success(
|
||||
null,
|
||||
);
|
||||
if (_historyController.tabController != null) {
|
||||
for (final item in _historyController.tabs) {
|
||||
try {
|
||||
Get.find<HistoryController>(
|
||||
tag: item.type,
|
||||
).loadingState.value = const Success(
|
||||
null,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
case 'viewed':
|
||||
currCtr().onDelViewedHistory();
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'pause',
|
||||
PopupMenuButton(
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => _historyController.baseCtr.onPauseHistory(context),
|
||||
child: Text(
|
||||
!_historyController.baseCtr.pauseStatus.value
|
||||
? '暂停观看记录'
|
||||
: '恢复观看记录',
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'clear',
|
||||
child: Text('清空观看记录'),
|
||||
PopupMenuItem(
|
||||
onTap: () => _historyController.baseCtr.onClearHistory(
|
||||
context,
|
||||
() {
|
||||
_historyController.loadingState.value = const Success(null);
|
||||
if (_historyController.tabController != null) {
|
||||
for (final item in _historyController.tabs) {
|
||||
try {
|
||||
Get.find<HistoryController>(
|
||||
tag: item.type,
|
||||
).loadingState.value = const Success(
|
||||
null,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
child: const Text('清空观看记录'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'viewed',
|
||||
child: Text('删除已看记录'),
|
||||
PopupMenuItem(
|
||||
onTap: currCtr().onDelViewedHistory,
|
||||
child: const Text('删除已看记录'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -182,7 +182,7 @@ class HistoryItem extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: 29,
|
||||
height: 29,
|
||||
child: PopupMenuButton<String>(
|
||||
child: PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '功能菜单',
|
||||
icon: Icon(
|
||||
@@ -191,61 +191,60 @@ class HistoryItem extends StatelessWidget {
|
||||
size: 18,
|
||||
),
|
||||
position: PopupMenuPosition.under,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
if (item.authorMid != null &&
|
||||
item.authorName?.isNotEmpty == true)
|
||||
PopupMenuItem<String>(
|
||||
onTap: () =>
|
||||
Get.toNamed('/member?mid=${item.authorMid}'),
|
||||
height: 35,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
MdiIcons.accountCircleOutline,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'访问:${item.authorName}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
],
|
||||
itemBuilder: (_) => [
|
||||
if (item.authorMid != null &&
|
||||
item.authorName?.isNotEmpty == true)
|
||||
PopupMenuItem(
|
||||
onTap: () =>
|
||||
Get.toNamed('/member?mid=${item.authorMid}'),
|
||||
height: 38,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
MdiIcons.accountCircleOutline,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
if (business != 'pgc' &&
|
||||
item.badge != '番剧' &&
|
||||
item.tagName?.contains('动画') != true &&
|
||||
business != 'live' &&
|
||||
business?.contains('article') != true)
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
final res = await UserHttp.toViewLater(
|
||||
bvid: item.history.bvid,
|
||||
);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
height: 35,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.watch_later_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('稍后再看', style: TextStyle(fontSize: 13)),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'访问:${item.authorName}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () => onDelete(item.kid!, business!),
|
||||
height: 35,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.close_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('删除记录', style: TextStyle(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (business != 'pgc' &&
|
||||
item.badge != '番剧' &&
|
||||
item.tagName?.contains('动画') != true &&
|
||||
business != 'live' &&
|
||||
business?.contains('article') != true)
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
final res = await UserHttp.toViewLater(
|
||||
bvid: item.history.bvid,
|
||||
);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
height: 38,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.watch_later_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('稍后再看', style: TextStyle(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onDelete(item.kid!, business!),
|
||||
height: 38,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.close_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('删除记录', style: TextStyle(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -165,7 +165,7 @@ class _LiveDmBlockPageState extends State<LiveDmBlockPage> {
|
||||
|
||||
Widget _buildKeyword(List list) {
|
||||
if (list.isEmpty) {
|
||||
return isPortrait ? errorWidget() : scrollErrorWidget();
|
||||
return scrollErrorWidget();
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.only(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/dialog/report.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/live.dart';
|
||||
@@ -98,10 +99,11 @@ class LiveRoomController extends GetxController {
|
||||
// dm
|
||||
LiveDmInfoData? dmInfo;
|
||||
List<RichTextItem>? savedDanmaku;
|
||||
RxList<DanmakuMsg> messages = <DanmakuMsg>[].obs;
|
||||
RxList<dynamic> messages = <dynamic>[].obs;
|
||||
late final Rx<SuperChatItem?> fsSC = Rx<SuperChatItem?>(null);
|
||||
late final RxList<SuperChatItem> superChatMsg = <SuperChatItem>[].obs;
|
||||
RxBool disableAutoScroll = false.obs;
|
||||
bool autoScroll = true;
|
||||
LiveMessageStream? _msgStream;
|
||||
late final ScrollController scrollController;
|
||||
late final RxInt pageIndex = 0.obs;
|
||||
@@ -297,6 +299,16 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
|
||||
void scrollToBottom([_]) {
|
||||
EasyThrottle.throttle(
|
||||
'liveDm',
|
||||
const Duration(milliseconds: 500),
|
||||
() => WidgetsBinding.instance.addPostFrameCallback(
|
||||
_scrollToBottom,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _scrollToBottom([_]) {
|
||||
if (scrollController.hasClients) {
|
||||
scrollController.animateTo(
|
||||
scrollController.position.maxScrollExtent,
|
||||
@@ -326,7 +338,7 @@ class LiveRoomController extends GetxController {
|
||||
messages.addAll(
|
||||
list.cast<Map<String, dynamic>>().map(DanmakuMsg.fromPrefetch),
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback(scrollToBottom);
|
||||
scrollToBottom();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
@@ -424,6 +436,19 @@ class LiveRoomController extends GetxController {
|
||||
..init();
|
||||
}
|
||||
|
||||
void addDm(dynamic msg, [DanmakuContentItem<DanmakuExtra>? item]) {
|
||||
messages.add(msg);
|
||||
|
||||
if (plPlayerController.showDanmaku) {
|
||||
if (item != null) {
|
||||
danmakuController?.addDanmaku(item);
|
||||
}
|
||||
if (autoScroll && !disableAutoScroll.value) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
void _danmakuListener(dynamic obj) {
|
||||
try {
|
||||
@@ -459,7 +484,7 @@ class LiveRoomController extends GetxController {
|
||||
name: extra['reply_uname'],
|
||||
);
|
||||
}
|
||||
messages.add(
|
||||
addDm(
|
||||
DanmakuMsg(
|
||||
name: name,
|
||||
uid: uid,
|
||||
@@ -471,31 +496,17 @@ class LiveRoomController extends GetxController {
|
||||
extra: liveExtra,
|
||||
reply: reply,
|
||||
),
|
||||
DanmakuContentItem(
|
||||
msg,
|
||||
color: DanmakuOptions.blockColorful
|
||||
? Colors.white
|
||||
: DmUtils.decimalToColor(extra['color']),
|
||||
type: DmUtils.getPosition(extra['mode']),
|
||||
// extra['send_from_me'] is invalid
|
||||
selfSend: isLogin && uid == mid,
|
||||
extra: liveExtra,
|
||||
),
|
||||
);
|
||||
|
||||
if (plPlayerController.showDanmaku) {
|
||||
danmakuController?.addDanmaku(
|
||||
DanmakuContentItem(
|
||||
msg,
|
||||
color: DanmakuOptions.blockColorful
|
||||
? Colors.white
|
||||
: DmUtils.decimalToColor(extra['color']),
|
||||
type: DmUtils.getPosition(extra['mode']),
|
||||
// extra['send_from_me'] is invalid
|
||||
selfSend: isLogin && uid == mid,
|
||||
extra: liveExtra,
|
||||
),
|
||||
);
|
||||
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']);
|
||||
@@ -505,6 +516,7 @@ class LiveRoomController extends GetxController {
|
||||
endTime: DateTime.now().millisecondsSinceEpoch ~/ 1000 + 10,
|
||||
);
|
||||
}
|
||||
addDm(item);
|
||||
break;
|
||||
case 'WATCHED_CHANGE':
|
||||
watchedShow.value = obj['data']['text_large'];
|
||||
@@ -587,4 +599,26 @@ class LiveRoomController extends GetxController {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reportSC(SuperChatItem item) {
|
||||
if (!Accounts.main.isLogin) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
autoWrapReportDialog(
|
||||
Get.context!,
|
||||
ReportOptions.liveDanmakuReport,
|
||||
(reasonType, reasonDesc, banUid) {
|
||||
return LiveHttp.superChatReport(
|
||||
id: item.id,
|
||||
roomId: roomId,
|
||||
uid: item.uid,
|
||||
msg: item.message,
|
||||
reason: ReportOptions.liveDanmakuReport['']![reasonType]!,
|
||||
ts: item.ts,
|
||||
token: item.token,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,21 +128,17 @@ class _ReplyPageState extends CommonRichTextPubPageState<LiveSendDmPanel> {
|
||||
),
|
||||
Container(
|
||||
height: 52,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
padding: const .symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: .spaceBetween,
|
||||
children: [
|
||||
emojiBtn,
|
||||
const Spacer(),
|
||||
Obx(
|
||||
() => FilledButton.tonal(
|
||||
onPressed: enablePublish.value ? onPublish : null,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
visualDensity: .compact,
|
||||
padding: const .symmetric(horizontal: 20, vertical: 10),
|
||||
),
|
||||
child: const Text('发送'),
|
||||
),
|
||||
@@ -195,7 +191,5 @@ class _ReplyPageState extends CommonRichTextPubPageState<LiveSendDmPanel> {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onMention([bool fromClick = false]) {
|
||||
return Future.syncValue(null);
|
||||
}
|
||||
Future<void>? onMention([bool fromClick = false]) => null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ 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/pages/video/introduction/ugc/widgets/selectable_text.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -12,13 +12,15 @@ class SuperChatCard extends StatefulWidget {
|
||||
const SuperChatCard({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.onRemove,
|
||||
this.onRemove,
|
||||
this.persistentSC = false,
|
||||
required this.onReport,
|
||||
});
|
||||
|
||||
final SuperChatItem item;
|
||||
final VoidCallback onRemove;
|
||||
final VoidCallback? onRemove;
|
||||
final bool persistentSC;
|
||||
final VoidCallback onReport;
|
||||
|
||||
@override
|
||||
State<SuperChatCard> createState() => _SuperChatCardState();
|
||||
@@ -40,7 +42,7 @@ class _SuperChatCardState extends State<SuperChatCard> {
|
||||
final offset = widget.item.endTime - now;
|
||||
if (offset > 0) {
|
||||
_remains = offset.obs;
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), _callback);
|
||||
_startTimer();
|
||||
} else {
|
||||
_remove();
|
||||
}
|
||||
@@ -56,7 +58,7 @@ class _SuperChatCardState extends State<SuperChatCard> {
|
||||
void _onRemove() {
|
||||
widget
|
||||
..item.expired = true
|
||||
..onRemove();
|
||||
..onRemove?.call();
|
||||
}
|
||||
|
||||
void _callback(_) {
|
||||
@@ -69,6 +71,10 @@ class _SuperChatCardState extends State<SuperChatCard> {
|
||||
}
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), _callback);
|
||||
}
|
||||
|
||||
void _cancelTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
@@ -80,61 +86,101 @@ class _SuperChatCardState extends State<SuperChatCard> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showMenu(Offset offset, SuperChatItem item) {
|
||||
final flag = _timer != null;
|
||||
if (flag) {
|
||||
_cancelTimer();
|
||||
}
|
||||
showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(offset.dx, offset.dy, offset.dx, 0),
|
||||
items: [
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: () => Get.toNamed('/member?mid=${item.uid}'),
|
||||
child: Text(
|
||||
'访问: ${item.userInfo.uname}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: widget.onReport,
|
||||
child: const Text(
|
||||
'举报',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
).whenComplete(() {
|
||||
if (flag && mounted) {
|
||||
_startTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final item = widget.item;
|
||||
final bottomColor = Utils.parseColor(item.backgroundBottomColor);
|
||||
final border = BorderSide(color: bottomColor);
|
||||
void showMenu(TapUpDetails e) => _showMenu(e.globalPosition, item);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: 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(
|
||||
GestureDetector(
|
||||
onTapUp: showMenu,
|
||||
onSecondaryTapUp: PlatformUtils.isDesktop ? showMenu : null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: 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: [
|
||||
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),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Text(
|
||||
item.userInfo.uname,
|
||||
style: TextStyle(
|
||||
color: Utils.parseColor(item.userInfo.nameColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"¥${item.price}",
|
||||
style: TextStyle(
|
||||
color: Utils.parseColor(item.backgroundPriceColor),
|
||||
Text(
|
||||
"¥${item.price}",
|
||||
style: TextStyle(
|
||||
color: Utils.parseColor(item.backgroundPriceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_remains != null)
|
||||
Obx(
|
||||
() => Text(
|
||||
_remains.toString(),
|
||||
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_remains != null)
|
||||
Obx(
|
||||
() => Text(
|
||||
_remains.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
@@ -145,9 +191,11 @@ class _SuperChatCardState extends State<SuperChatCard> {
|
||||
color: bottomColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: selectableText(
|
||||
item.message,
|
||||
style: TextStyle(color: Utils.parseColor(item.messageFontColor)),
|
||||
child: SelectionArea(
|
||||
child: Text(
|
||||
item.message,
|
||||
style: TextStyle(color: Utils.parseColor(item.messageFontColor)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@ 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';
|
||||
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
||||
|
||||
class SuperChatPanel extends StatefulWidget {
|
||||
const SuperChatPanel({
|
||||
@@ -48,9 +48,10 @@ class _SuperChatPanelState extends DebounceStreamState<SuperChatPanel, bool>
|
||||
item: item,
|
||||
onRemove: () => ctr?.add(true),
|
||||
persistentSC: persistentSC,
|
||||
onReport: () => widget.controller.reportSC(item),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,17 +286,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
right: 0,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
_liveRoomController.fsSC.value = SuperChatItem.fromJson({
|
||||
"id": Utils.random.nextInt(2147483647),
|
||||
"price": 66,
|
||||
"end_time":
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5,
|
||||
"message": Utils.generateRandomString(55),
|
||||
"user_info": {
|
||||
"face": "",
|
||||
"uname": Utils.generateRandomString(8),
|
||||
},
|
||||
});
|
||||
final item = SuperChatItem.random;
|
||||
_liveRoomController
|
||||
..fsSC.value = item
|
||||
..addDm(item);
|
||||
},
|
||||
child: const Text('add superchat'),
|
||||
),
|
||||
@@ -332,6 +325,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
child: SuperChatCard(
|
||||
item: item,
|
||||
onRemove: () => _liveRoomController.fsSC.value = null,
|
||||
onReport: () => _liveRoomController.reportSC(item),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@@ -680,7 +674,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
clampDouble(maxHeight / maxWidth * 1.08, 0.56, 0.7) * maxWidth;
|
||||
final rightWidth = min(400.0, maxWidth - videoWidth - padding.horizontal);
|
||||
videoWidth = maxWidth - rightWidth - padding.horizontal;
|
||||
final videoHeight = maxHeight - padding.top;
|
||||
final videoHeight = maxHeight - padding.top - kToolbarHeight;
|
||||
final width = isFullScreen ? maxWidth : videoWidth;
|
||||
final height = isFullScreen ? maxHeight - padding.top : videoHeight;
|
||||
return Padding(
|
||||
@@ -1023,22 +1017,20 @@ class _LiveDanmakuState extends State<LiveDanmaku> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(
|
||||
() {
|
||||
return AnimatedOpacity(
|
||||
opacity: plPlayerController.enableShowDanmaku.value
|
||||
? plPlayerController.danmakuOpacity.value
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: DanmakuScreen<DanmakuExtra>(
|
||||
createdController: (e) {
|
||||
widget.liveRoomController.danmakuController =
|
||||
plPlayerController.danmakuController = e;
|
||||
},
|
||||
option: DanmakuOptions.get(notFullscreen: widget.notFullscreen),
|
||||
size: widget.size,
|
||||
),
|
||||
);
|
||||
},
|
||||
() => AnimatedOpacity(
|
||||
opacity: plPlayerController.enableShowDanmaku.value
|
||||
? plPlayerController.danmakuOpacity.value
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: DanmakuScreen<DanmakuExtra>(
|
||||
createdController: (e) {
|
||||
widget.liveRoomController.danmakuController =
|
||||
plPlayerController.danmakuController = e;
|
||||
},
|
||||
option: DanmakuOptions.get(notFullscreen: widget.notFullscreen),
|
||||
size: widget.size,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:PiliPlus/common/widgets/flutter/popup_menu.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/live.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_superchat/item.dart';
|
||||
import 'package:PiliPlus/pages/live_room/controller.dart';
|
||||
import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart';
|
||||
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -50,51 +51,74 @@ class LiveRoomChatPanel extends StatelessWidget {
|
||||
key: const PageStorageKey('live-chat'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
controller: liveRoomController.scrollController,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemCount: liveRoomController.messages.length,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
itemBuilder: (_, index) {
|
||||
final item = liveRoomController.messages[index];
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${item.name}: ',
|
||||
style: TextStyle(
|
||||
color: nameColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
recognizer: item.uid == 0
|
||||
? null
|
||||
: (TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
_showMsgDialog(context, item)),
|
||||
if (item is DanmakuMsg) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Builder(
|
||||
builder: (itemContext) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
if (item.reply case final reply?)
|
||||
TextSpan(
|
||||
text: '@${reply.name} ',
|
||||
style: TextStyle(color: primary, fontSize: 14),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
Get.toNamed('/member?mid=${reply.mid}'),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(14),
|
||||
),
|
||||
_buildMsg(devicePixelRatio, item),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${item.name}: ',
|
||||
style: TextStyle(
|
||||
color: nameColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
recognizer: item.uid == 0
|
||||
? null
|
||||
: (TapGestureRecognizer()
|
||||
..onTapDown = (e) => _showMsgMenu(
|
||||
context,
|
||||
itemContext,
|
||||
e,
|
||||
item,
|
||||
)),
|
||||
),
|
||||
if (item.reply case final reply?)
|
||||
TextSpan(
|
||||
text: '@${reply.name} ',
|
||||
style: TextStyle(
|
||||
color: primary,
|
||||
fontSize: 14,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () =>
|
||||
Get.toNamed('/member?mid=${reply.mid}'),
|
||||
),
|
||||
_buildMsg(devicePixelRatio, item),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
if (item is SuperChatItem) {
|
||||
return SuperChatCard(
|
||||
item: item,
|
||||
persistentSC: true,
|
||||
onReport: () => liveRoomController.reportSC(item),
|
||||
);
|
||||
}
|
||||
throw item.runtimeType;
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -104,20 +128,10 @@ class LiveRoomChatPanel extends StatelessWidget {
|
||||
right: 0,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
liveRoomController.superChatMsg.insert(
|
||||
0,
|
||||
SuperChatItem.fromJson({
|
||||
"id": Utils.random.nextInt(2147483647),
|
||||
"price": 66,
|
||||
"end_time":
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000 + 5,
|
||||
"message": "message message message message message",
|
||||
"user_info": {
|
||||
"face": "",
|
||||
"uname": "UNAME",
|
||||
},
|
||||
}),
|
||||
);
|
||||
final item = SuperChatItem.random;
|
||||
liveRoomController
|
||||
..superChatMsg.insert(0, item)
|
||||
..addDm(item);
|
||||
},
|
||||
child: const Text('add superchat'),
|
||||
),
|
||||
@@ -273,82 +287,89 @@ class LiveRoomChatPanel extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void _showMsgDialog(BuildContext context, DanmakuMsg item) {
|
||||
showDialog(
|
||||
void _showMsgMenu(
|
||||
BuildContext context,
|
||||
BuildContext itemContext,
|
||||
TapDownDetails details,
|
||||
DanmakuMsg item,
|
||||
) {
|
||||
final dx = details.globalPosition.dx;
|
||||
final renderBox = itemContext.findRenderObject() as RenderBox;
|
||||
final dy = renderBox.localToGlobal(renderBox.size.bottomLeft(.zero)).dy;
|
||||
final autoScroll =
|
||||
liveRoomController.autoScroll &&
|
||||
!liveRoomController.disableAutoScroll.value;
|
||||
if (autoScroll) {
|
||||
liveRoomController.autoScroll = false;
|
||||
}
|
||||
showMenu(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
clipBehavior: .hardEdge,
|
||||
contentPadding: const .symmetric(vertical: 12),
|
||||
constraints: const BoxConstraints(minWidth: 280, maxWidth: 320),
|
||||
title: Column(
|
||||
spacing: 4,
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
Text(
|
||||
item.text,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: ColorScheme.of(context).outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
position: RelativeRect.fromLTRB(dx, dy, dx, 0),
|
||||
items: <PopupMenuEntry<Never>>[
|
||||
CustomPopupMenuItem(
|
||||
height: 38,
|
||||
child: Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get
|
||||
..back()
|
||||
..toNamed('/member?mid=${item.uid}');
|
||||
},
|
||||
title: const Text('去TA的个人空间', style: TextStyle(fontSize: 14)),
|
||||
const CustomPopupMenuDivider(height: 1),
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: () => Get.toNamed('/member?mid=${item.uid}'),
|
||||
child: const Text(
|
||||
'去TA的个人空间',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
onAtUser(item);
|
||||
},
|
||||
title: const Text('@TA', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: () => onAtUser(item),
|
||||
child: const Text(
|
||||
'@TA',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('屏蔽发送者', style: TextStyle(fontSize: 14)),
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
if (!Accounts.main.isLogin) return;
|
||||
final res = await LiveHttp.liveShieldUser(
|
||||
uid: item.uid,
|
||||
roomid: roomId,
|
||||
type: 1,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('屏蔽成功');
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: () async {
|
||||
if (!Accounts.main.isLogin) return;
|
||||
final res = await LiveHttp.liveShieldUser(
|
||||
uid: item.uid,
|
||||
roomid: roomId,
|
||||
type: 1,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('屏蔽成功');
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'屏蔽发送者',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('举报选中弹幕', style: TextStyle(fontSize: 14)),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
HeaderControl.reportLiveDanmaku(
|
||||
context,
|
||||
roomId: roomId,
|
||||
msg: item.text,
|
||||
extra: item.extra,
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: 38,
|
||||
onTap: () => HeaderControl.reportLiveDanmaku(
|
||||
context,
|
||||
roomId: roomId,
|
||||
msg: item.text,
|
||||
extra: item.extra,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
child: const Text(
|
||||
'举报选中弹幕',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
).whenComplete(() {
|
||||
if (autoScroll && context.mounted) {
|
||||
liveRoomController
|
||||
..autoScroll = true
|
||||
..scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class _LogsPageState extends State<LogsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearLogsHandle() async {
|
||||
Future<void> clearLogs() async {
|
||||
if (await LoggerUtils.clearLogs()) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -112,57 +112,45 @@ class _LogsPageState extends State<LogsPage> {
|
||||
appBar: AppBar(
|
||||
title: const Text('日志'),
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (String type) {
|
||||
switch (type) {
|
||||
case 'log':
|
||||
enableLog = !enableLog;
|
||||
GStorage.setting.put(SettingBoxKey.enableLog, enableLog);
|
||||
SmartDialog.showToast('已${enableLog ? '开启' : '关闭'},重启生效');
|
||||
break;
|
||||
case 'copy':
|
||||
copyLogs();
|
||||
break;
|
||||
case 'feedback':
|
||||
PageUtils.launchURL('${Constants.sourceCodeUrl}/issues');
|
||||
break;
|
||||
case 'clear':
|
||||
latestLog = null;
|
||||
clearLogsHandle();
|
||||
break;
|
||||
default:
|
||||
if (kDebugMode) {
|
||||
Timer.periodic(const Duration(milliseconds: 3500), (timer) {
|
||||
PopupMenuButton(
|
||||
itemBuilder: (_) => [
|
||||
if (kDebugMode)
|
||||
PopupMenuItem(
|
||||
onTap: () => Timer.periodic(
|
||||
const Duration(milliseconds: 3500),
|
||||
(timer) {
|
||||
Utils.reportError('Manual');
|
||||
if (timer.tick > 3) {
|
||||
timer.cancel();
|
||||
if (mounted) getLog();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
if (kDebugMode)
|
||||
const PopupMenuItem<String>(
|
||||
value: 'assert',
|
||||
child: Text('引发错误'),
|
||||
},
|
||||
),
|
||||
child: const Text('引发错误'),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'log',
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
enableLog = !enableLog;
|
||||
GStorage.setting.put(SettingBoxKey.enableLog, enableLog);
|
||||
SmartDialog.showToast('已${enableLog ? '开启' : '关闭'},重启生效');
|
||||
},
|
||||
child: Text('${enableLog ? '关闭' : '开启'}日志'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'copy',
|
||||
child: Text('复制日志'),
|
||||
PopupMenuItem(
|
||||
onTap: copyLogs,
|
||||
child: const Text('复制日志'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'feedback',
|
||||
child: Text('错误反馈'),
|
||||
PopupMenuItem(
|
||||
onTap: () =>
|
||||
PageUtils.launchURL('${Constants.sourceCodeUrl}/issues'),
|
||||
child: const Text('错误反馈'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'clear',
|
||||
child: Text('清空日志'),
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
latestLog = null;
|
||||
clearLogs();
|
||||
},
|
||||
child: const Text('清空日志'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ Widget selectableText(
|
||||
return SelectableText(
|
||||
style: style,
|
||||
text,
|
||||
scrollPhysics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,5 +35,6 @@ Widget selectableRichText(
|
||||
return SelectableText.rich(
|
||||
style: style,
|
||||
textSpan,
|
||||
scrollPhysics: const NeverScrollableScrollPhysics(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -359,9 +359,7 @@ class _WhisperDetailPageState
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onMention([bool fromClick = false]) {
|
||||
return Future.syncValue(null);
|
||||
}
|
||||
Future<void>? onMention([bool fromClick = false]) => null;
|
||||
|
||||
@override
|
||||
void onSave() {}
|
||||
|
||||
Reference in New Issue
Block a user