diff --git a/lib/common/widgets/dialog/report.dart b/lib/common/widgets/dialog/report.dart index 77bf05888..851461a72 100644 --- a/lib/common/widgets/dialog/report.dart +++ b/lib/common/widgets/dialog/report.dart @@ -26,63 +26,81 @@ Future autoWrapReportDialog( right: 16, bottom: 10, ), - content: Form( - key: key, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: SingleChildScrollView( - child: AnimatedSize( - duration: const Duration(milliseconds: 200), - child: Builder( - builder: (context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.only( + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: SingleChildScrollView( + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + child: Builder( + builder: (context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only( + left: 22, + right: 22, + bottom: 5, + ), + child: Text('请选择举报的理由:'), + ), + RadioGroup( + onChanged: (value) { + reasonType = value; + (context as Element).markNeedsBuild(); + }, + groupValue: reasonType, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options.entries.map((entry) { + return WrapRadioOptionsGroup( + groupTitle: entry.key, + options: entry.value, + ); + }).toList(), + ), + ), + if (reasonType == 0) + Padding( + padding: const EdgeInsets.only( left: 22, + top: 5, right: 22, - bottom: 5, ), - child: Text('请选择举报的理由:'), - ), - RadioGroup( - onChanged: (value) { - reasonType = value; - (context as Element).markNeedsBuild(); - }, - groupValue: reasonType, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: options.entries.map((entry) { - return WrapRadioOptionsGroup( - groupTitle: entry.key, - options: entry.value, - ); - }).toList(), + child: Form( + key: key, + child: TextFormField( + autofocus: true, + minLines: 2, + maxLines: 4, + initialValue: reasonDesc, + decoration: const InputDecoration( + labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息', + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + ), + onChanged: (value) => reasonDesc = value, + validator: (value) => + value.isNullOrEmpty ? '理由不能为空' : null, + ), ), ), - if (reasonType == 0) - ReasonField( - onChanged: (value) => reasonDesc = value, - ), - ], - ), + ], ), ), ), ), - Padding( - padding: const EdgeInsets.only(left: 14, top: 6), - child: CheckBoxText( - text: '拉黑该用户', - onChanged: (value) => banUid = value, - ), + ), + Padding( + padding: const EdgeInsets.only(left: 14, top: 6), + child: CheckBoxText( + text: '拉黑该用户', + onChanged: (value) => banUid = value, ), - ], - ), + ), + ], ), actions: [ TextButton( @@ -121,46 +139,6 @@ Future autoWrapReportDialog( ); } -class ReasonField extends StatefulWidget { - final ValueChanged onChanged; - String? _validator(String? value) => value.isNullOrEmpty ? '理由不能为空' : null; - - const ReasonField({super.key, required this.onChanged}); - - @override - State createState() => _ReasonFieldState(); -} - -class _ReasonFieldState extends State { - final _controller = TextEditingController(); - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 22, top: 5, right: 22), - child: TextFormField( - controller: _controller, - autofocus: true, - minLines: 4, - maxLines: 4, - decoration: const InputDecoration( - labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息', - border: OutlineInputBorder(), - contentPadding: EdgeInsets.all(10), - ), - onChanged: widget.onChanged, - validator: widget._validator, - ), - ); - } -} - class CheckBoxText extends StatefulWidget { final String text; final ValueChanged onChanged; diff --git a/lib/common/widgets/list_tile.dart b/lib/common/widgets/list_tile.dart index 31e80a68a..22056f15b 100644 --- a/lib/common/widgets/list_tile.dart +++ b/lib/common/widgets/list_tile.dart @@ -1314,10 +1314,10 @@ class _RenderListTile extends RenderBox Iterable get children { final RenderBox? title = childForSlot(_ListTileSlot.title); return [ - if (leading != null) leading!, - if (title != null) title, - if (subtitle != null) subtitle!, - if (trailing != null) trailing!, + ?leading, + ?title, + ?subtitle, + ?trailing, ]; } diff --git a/lib/common/widgets/loading_widget/http_error.dart b/lib/common/widgets/loading_widget/http_error.dart index c600f92a2..ddf48fb71 100644 --- a/lib/common/widgets/loading_widget/http_error.dart +++ b/lib/common/widgets/loading_widget/http_error.dart @@ -19,7 +19,7 @@ class HttpError extends StatelessWidget { Widget build(BuildContext context) { return isSliver ? SliverToBoxAdapter(child: content(context)) - : content(context); + : SizedBox(width: double.infinity, child: content(context)); } Widget content(BuildContext context) { diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 6e5877803..ccfe72e05 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -479,6 +479,7 @@ class AuthorPanel extends StatelessWidget { mid: moduleAuthor.mid, dynId: item.idStr, reasonType: reasonType, + reasonDesc: reasonType == 0 ? reasonDesc : null, ); }, ); diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index 1215fbb6b..099aab9d7 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -172,9 +172,7 @@ class _VotePanelState extends State { CheckBoxText( text: '匿名', selected: anonymity, - onChanged: (val) { - anonymity = val; - }, + onChanged: (val) => anonymity = val, ), ], ); diff --git a/lib/pages/match_info/controller.dart b/lib/pages/match_info/controller.dart index 3248a60ab..3d50110bc 100644 --- a/lib/pages/match_info/controller.dart +++ b/lib/pages/match_info/controller.dart @@ -1,5 +1,3 @@ -import 'dart:ui' show Offset; - import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/match.dart'; import 'package:PiliPlus/models_new/match/match_info/contest.dart'; @@ -18,9 +16,6 @@ class MatchInfoController extends CommonDynController { final Rx> infoState = LoadingState.loading().obs; - @override - Offset get fabOffset => const Offset(0, 2); - @override void onInit() { super.onInit(); diff --git a/lib/pages/music/controller.dart b/lib/pages/music/controller.dart index 621ae0fa4..5b08f48e9 100644 --- a/lib/pages/music/controller.dart +++ b/lib/pages/music/controller.dart @@ -21,7 +21,7 @@ class MusicDetailController extends CommonDynController { bool get showDynActionBar => Pref.showDynActionBar; String get shareUrl => - 'https://music.bilibili.com/h5/music-detail?music_id=${musicId}'; + 'https://music.bilibili.com/h5/music-detail?music_id=$musicId'; @override void onInit() { diff --git a/lib/pages/video/post_panel/popup_menu_text.dart b/lib/pages/video/post_panel/popup_menu_text.dart index bd7c64845..a396f899c 100644 --- a/lib/pages/video/post_panel/popup_menu_text.dart +++ b/lib/pages/video/post_panel/popup_menu_text.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +typedef PopupMenuItemSelected = bool Function(T value); + class PopupMenuText extends StatefulWidget { final String title; final T initialValue; @@ -24,9 +26,17 @@ class PopupMenuText extends StatefulWidget { class _PopupMenuTextState extends State> { late T select = widget.initialValue; + @override + void didUpdateWidget(PopupMenuText oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialValue != widget.initialValue) { + select = widget.initialValue; + } + } + @override Widget build(BuildContext context) { - final theme = Theme.of(context); + final secondary = Theme.of(context).colorScheme.secondary; return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -35,10 +45,11 @@ class _PopupMenuTextState extends State> { initialValue: select, onSelected: (value) { if (value == select) return; - setState(() { - select = value; - widget.onSelected(value); - }); + if (!widget.onSelected(value)) { + setState(() { + select = value; + }); + } }, itemBuilder: widget.itemBuilder, child: Row( @@ -49,7 +60,7 @@ class _PopupMenuTextState extends State> { style: TextStyle( height: 1, fontSize: 14, - color: theme.colorScheme.secondary, + color: secondary, ), strutStyle: const StrutStyle( height: 1, @@ -59,7 +70,7 @@ class _PopupMenuTextState extends State> { Icon( MdiIcons.unfoldMoreHorizontal, size: MediaQuery.textScalerOf(context).scale(14), - color: theme.colorScheme.secondary, + color: secondary, ), ], ), diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index f55734a07..356fa8b74 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -57,7 +57,7 @@ class PostPanel extends CommonCollapseSlidePage { static Widget segmentWidget( ThemeData theme, { required PostSegmentModel item, - required double currentPos, + required double Function() currentPos, // get real-time pos required double videoDuration, }) { Widget segment(bool isFirst) => Builder( @@ -81,7 +81,7 @@ class PostPanel extends CommonCollapseSlidePage { updateSegment( isFirst: isFirst, item: item, - value: currentPos, + value: currentPos(), ); (context as Element).markNeedsBuild(); }, @@ -377,82 +377,105 @@ class _PostPanelState extends CommonCollapseSlidePageState { color: theme.colorScheme.onInverseSurface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (item.actionType != ActionType.full) - PostPanel.segmentWidget( - theme, - item: item, - currentPos: currentPos, - videoDuration: videoDuration, - ), - Wrap( - runSpacing: 8, - spacing: 16, - children: [ - PopupMenuText( - title: '分类', - initialValue: item.category, - onSelected: (e) { - item.category = e; - List constraintList = e.toActionType; - if (!constraintList.contains(item.actionType)) { - item.actionType = constraintList.first; - } - switch (e) { - case SegmentType.poi_highlight: - PostPanel.updateSegment( - isFirst: false, - item: item, - value: item.segment.first, - ); - break; - case SegmentType.exclusive_access: + child: Builder( + builder: (context) => Column( + spacing: 8, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (item.actionType != ActionType.full) + PostPanel.segmentWidget( + theme, + item: item, + currentPos: () => currentPos, + videoDuration: videoDuration, + ), + Wrap( + runSpacing: 8, + spacing: 16, + children: [ + PopupMenuText( + title: '分类', + initialValue: item.category, + onSelected: (e) { + bool flag = false; + if (item.category == SegmentType.exclusive_access || + item.category == SegmentType.poi_highlight) { + flag = true; + } + item.category = e; + List constraintList = e.toActionType; + if (!constraintList.contains(item.actionType)) { + item.actionType = constraintList.first; + flag = true; + } + switch (e) { + case SegmentType.poi_highlight: + PostPanel.updateSegment( + isFirst: false, + item: item, + value: item.segment.first, + ); + break; + case SegmentType.exclusive_access: + PostPanel.updateSegment( + isFirst: true, + item: item, + value: 0, + ); + break; + default: + } + if (flag) { + (context as Element).markNeedsBuild(); + } + return flag; + }, + itemBuilder: (context) => SegmentType.values + .map( + (e) => + PopupMenuItem(value: e, child: Text(e.title)), + ) + .toList(), + getSelectTitle: (category) => category.title, + ), + PopupMenuText( + title: '行为类别', + initialValue: item.actionType, + onSelected: (e) { + bool flag = false; + if (item.actionType == ActionType.full) { + flag = true; + } + item.actionType = e; + if (e == ActionType.full) { + flag = true; PostPanel.updateSegment( isFirst: true, item: item, value: 0, ); - break; - default: - } - }, - itemBuilder: (context) => SegmentType.values - .map( - (e) => PopupMenuItem(value: e, child: Text(e.title)), - ) - .toList(), - getSelectTitle: (category) => category.title, - ), - PopupMenuText( - title: '行为类别', - initialValue: item.actionType, - onSelected: (e) { - item.actionType = e; - if (e == ActionType.full) { - PostPanel.updateSegment( - isFirst: true, - item: item, - value: 0, - ); - } - }, - itemBuilder: (context) => ActionType.values - .map( - (e) => PopupMenuItem( - enabled: item.category.toActionType.contains(e), - value: e, - child: Text(e.title), - ), - ) - .toList(), - getSelectTitle: (i) => i.title, - ), - ], - ), - ], + } + if (flag) { + (context as Element).markNeedsBuild(); + } + return flag; + }, + itemBuilder: (context) => ActionType.values + .map( + (e) => PopupMenuItem( + enabled: item.category.toActionType.contains(e), + value: e, + child: Text(e.title), + ), + ) + .toList(), + getSelectTitle: (i) => i.title, + ), + ], + ), + ], + ), ), ), Positioned( @@ -492,7 +515,9 @@ class _PostPanelState extends CommonCollapseSlidePageState { await Future.delayed(Duration(milliseconds: delay)); } videoCtr.seek( - Duration(milliseconds: (item.segment.second * 1000).round()), + Duration( + milliseconds: (item.segment.second * 1000).round(), + ), ); } }, diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 16c3ef7e7..5b6ff3ce5 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -38,6 +38,7 @@ class _WhisperPageState extends State { onPressed: () => e.type.action( context: context, controller: _controller, + item: e, ), icon: e.type.icon, ); @@ -57,6 +58,7 @@ class _WhisperPageState extends State { onTap: () => e.type.action( context: context, controller: _controller, + item: e, ), child: Row( children: [ diff --git a/lib/pages/whisper_secondary/view.dart b/lib/pages/whisper_secondary/view.dart index 9ba8abc50..c8397ddb6 100644 --- a/lib/pages/whisper_secondary/view.dart +++ b/lib/pages/whisper_secondary/view.dart @@ -47,6 +47,7 @@ class _WhisperSecPageState extends State { onTap: () => e.type.action( context: context, controller: _controller, + item: e, ), child: Row( children: [ diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 3459aa78b..a70e98158 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -1906,7 +1906,7 @@ class _PLVideoPlayerState extends State PostPanel.segmentWidget( theme, item: model, - currentPos: currentPos, + currentPos: () => currentPos, videoDuration: duration, ), PopupMenuText( @@ -1916,6 +1916,7 @@ class _PLVideoPlayerState extends State final video = videoCtr.findVideoByQa(value); url = video.baseUrl; qa = video.quality; + return false; }, itemBuilder: (context) => videoInfo.supportFormats! .map( @@ -1932,9 +1933,8 @@ class _PLVideoPlayerState extends State title: 'webp预设', initialValue: preset, onSelected: (value) { - if (preset == value) return; preset = value; - (context as Element).markNeedsBuild(); + return false; }, itemBuilder: (context) => WebpPreset.values .map((i) => PopupMenuItem(value: i, child: Text(i.name))) diff --git a/lib/utils/extension.dart b/lib/utils/extension.dart index 6288205d7..5ae32c2c1 100644 --- a/lib/utils/extension.dart +++ b/lib/utils/extension.dart @@ -1,11 +1,13 @@ import 'dart:io'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' show ThreeDotItem; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart' show IMSettingType, ThreeDotItemType; import 'package:PiliPlus/pages/common/common_whisper_controller.dart'; import 'package:PiliPlus/pages/contact/view.dart'; import 'package:PiliPlus/pages/whisper_settings/view.dart'; +import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -172,6 +174,7 @@ extension ThreeDotItemTypeExt on ThreeDotItemType { void action({ required BuildContext context, required CommonWhisperController controller, + required ThreeDotItem item, }) { switch (this) { case ThreeDotItemType.THREE_DOT_ITEM_TYPE_READ_ALL: @@ -195,15 +198,26 @@ extension ThreeDotItemTypeExt on ThreeDotItemType { ), ); case ThreeDotItemType.THREE_DOT_ITEM_TYPE_UP_HELPER: - Get.toNamed( - '/whisperDetail', - arguments: { - 'talkerId': 844424930131966, - 'name': 'UP主小助手', - 'face': - 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', - }, - ); + dynamic talkerId = PiliScheme.uriDigitRegExp + .firstMatch(item.url) + ?.group(1); + if (talkerId != null) { + talkerId = int.parse(talkerId); + Get.toNamed( + '/whisperDetail', + arguments: { + 'talkerId': talkerId, + 'name': item.title, + 'face': switch (talkerId) { + 844424930131966 => + 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', + 844424930131964 => + 'https://i0.hdslb.com/bfs/im_new/58eda511672db078466e7ab8db22a95c1503684976.png', + _ => item.icon, + }, + }, + ); + } case ThreeDotItemType.THREE_DOT_ITEM_TYPE_CONTACTS: Get.to(const ContactPage(isFromSelect: false)); default: