diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index ee1599b48..bd7c66962 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -266,7 +266,7 @@ class VideoCustomActions { MineController.anonymity.value ? const Icon(MdiIcons.incognitoOff, size: 16) : const Icon(MdiIcons.incognito, size: 16), - () => MineController.onChangeAnonymity(context), + MineController.onChangeAnonymity, ) ]; } diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index c09e1bbbe..67f47cea6 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -662,7 +662,7 @@ class _ArticlePageState extends State SizedBox( height: 35, child: TextButton.icon( - onPressed: () => _articleCtr.queryBySort(), + onPressed: _articleCtr.queryBySort, icon: const Icon(Icons.sort, size: 16), label: Obx( () => Text( diff --git a/lib/pages/common/common_intro_controller.dart b/lib/pages/common/common_intro_controller.dart index 8db8f92a9..097963444 100644 --- a/lib/pages/common/common_intro_controller.dart +++ b/lib/pages/common/common_intro_controller.dart @@ -1,8 +1,6 @@ import 'package:PiliPlus/models_new/fav/fav_folder/data.dart'; -import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/models_new/video/video_tag/data.dart'; import 'package:PiliPlus/services/account_service.dart'; -import 'package:PiliPlus/utils/feed_back.dart'; import 'package:get/get.dart'; abstract class CommonIntroController extends GetxController { @@ -25,13 +23,4 @@ abstract class CommonIntroController extends GetxController { Future queryVideoInFolder(); Future actionFavVideo(); - - void onChoose(bool checkValue, int index) { - feedBack(); - FavFolderInfo item = favFolderData.value.list![index]; - item - ..favState = checkValue ? 1 : 0 - ..mediaCount = checkValue ? item.mediaCount + 1 : item.mediaCount - 1; - favFolderData.refresh(); - } } diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 5ae7d252f..9f5584cda 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -125,8 +125,9 @@ class _UpPanelState extends State { } void _onSelect(UserItem data) { - widget.dynamicsController.currentMid = data.mid; - widget.dynamicsController.onSelectUp(data.mid); + widget.dynamicsController + ..currentMid = data.mid + ..onSelectUp(data.mid); data.hasUpdate = false; diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index bca888593..9db480f22 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -138,28 +138,40 @@ class _VotePanelState extends State { ]; Widget _buildOptions(int index) { - final opt = _voteInfo.options[index]; - final selected = groupValue.contains(opt.optidx); return Padding( padding: const EdgeInsets.symmetric(vertical: 4), - child: PercentageChip( - label: opt.optdesc!, - percentage: _showPercentage ? _percentage[index] : null, - selected: selected, - onSelected: !_enabled || (groupValue.length >= _maxCnt && !selected) - ? null - : (value) => _onSelected(value, opt.optidx!), + child: Builder( + builder: (context) { + final opt = _voteInfo.options[index]; + final selected = groupValue.contains(opt.optidx); + return PercentageChip( + label: opt.optdesc!, + percentage: _showPercentage ? _percentage[index] : null, + selected: selected, + onSelected: !_enabled || (groupValue.length >= _maxCnt && !selected) + ? null + : (value) => _onSelected(context, value, opt.optidx!), + ); + }, ), ); } - void _onSelected(bool value, int optidx) { + void _onSelected(BuildContext context, bool value, int optidx) { + bool isMax = groupValue.length >= _maxCnt; if (value) { groupValue.add(optidx); } else { groupValue.remove(optidx); } - setState(() {}); + if ((isMax && + _maxCnt != _voteInfo.options.length && + groupValue.length < _maxCnt) || + (groupValue.length >= _maxCnt && _maxCnt < _voteInfo.options.length)) { + setState(() {}); + } else { + (context as Element).markNeedsBuild(); + } } Widget _buildContext() { diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 567a12c20..7f0e482ea 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -670,7 +670,7 @@ class _DynamicDetailPageState extends State SizedBox( height: 35, child: TextButton.icon( - onPressed: () => _controller.queryBySort(), + onPressed: _controller.queryBySort, icon: Icon( Icons.sort, size: 16, diff --git a/lib/pages/fav_create/view.dart b/lib/pages/fav_create/view.dart index ada9cfc1a..a55500eae 100644 --- a/lib/pages/fav_create/view.dart +++ b/lib/pages/fav_create/view.dart @@ -83,12 +83,12 @@ class _CreateFavPageState extends State { privacy: _isPublic ? 0 : 1, cover: _cover ?? '', intro: _introController.text, - ).then((data) { - if (data['status']) { - Get.back(result: data['data']); + ).then((res) { + if (res['status']) { + Get.back(result: res['data']); SmartDialog.showToast('${_mediaId != null ? '编辑' : '创建'}成功'); } else { - SmartDialog.showToast(data['msg']); + SmartDialog.showToast(res['msg']); } }); }, @@ -117,7 +117,7 @@ class _CreateFavPageState extends State { ); } - Future _pickImg(ThemeData theme) async { + Future _pickImg(BuildContext context, ThemeData theme) async { try { XFile? pickedFile = await _imagePicker.pickImage( source: ImageSource.gallery, @@ -154,12 +154,12 @@ class _CreateFavPageState extends State { path: croppedFile.path, bucket: 'medialist', dir: 'cover', - ).then((data) { - if (data['status']) { - _cover = data['data']['location']; - setState(() {}); + ).then((res) { + if (res['status']) { + _cover = res['data']['location']; + (context as Element).markNeedsBuild(); } else { - SmartDialog.showToast(data['msg']); + SmartDialog.showToast(res['msg']); } }); } @@ -175,87 +175,91 @@ class _CreateFavPageState extends State { child: Column( children: [ if (_attr == null || !FavUtil.isDefaultFav(_attr!)) ...[ - ListTile( - tileColor: theme.colorScheme.onInverseSurface, - onTap: () { - EasyThrottle.throttle( - 'imagePicker', const Duration(milliseconds: 500), () { - if (_cover?.isNotEmpty == true) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - clipBehavior: Clip.hardEdge, - contentPadding: - const EdgeInsets.fromLTRB(0, 12, 0, 12), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - dense: true, - onTap: () { - Get.back(); - _pickImg(theme); - }, - title: const Text( - '替换封面', - style: TextStyle(fontSize: 14), - ), + Builder( + builder: (context) { + return ListTile( + tileColor: theme.colorScheme.onInverseSurface, + onTap: () { + EasyThrottle.throttle( + 'imagePicker', const Duration(milliseconds: 500), () { + if (_cover?.isNotEmpty == true) { + showDialog( + context: context, + builder: (_) { + return AlertDialog( + clipBehavior: Clip.hardEdge, + contentPadding: + const EdgeInsets.fromLTRB(0, 12, 0, 12), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + dense: true, + onTap: () { + Get.back(); + _pickImg(context, theme); + }, + title: const Text( + '替换封面', + style: TextStyle(fontSize: 14), + ), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + _cover = null; + (context as Element).markNeedsBuild(); + }, + title: const Text( + '移除封面', + style: TextStyle(fontSize: 14), + ), + ), + ], ), - ListTile( - dense: true, - onTap: () { - Get.back(); - _cover = null; - setState(() {}); - }, - title: const Text( - '移除封面', - style: TextStyle(fontSize: 14), - ), - ), - ], - ), + ); + }, ); - }, - ); - } else { - _pickImg(theme); - } - }); - }, - leading: Text( - '封面', - style: leadingStyle, - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (_cover?.isNotEmpty == true) - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: LayoutBuilder( - builder: (context, constraints) { - return ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(6)), - child: CachedNetworkImage( - imageUrl: ImageUtil.thumbnailUrl(_cover!), - height: constraints.maxHeight, - width: constraints.maxHeight * 16 / 9, - fit: BoxFit.cover, - ), - ); - }, - ), - ), - const SizedBox(width: 10), - Icon( - Icons.keyboard_arrow_right, - color: theme.colorScheme.outline, + } else { + _pickImg(context, theme); + } + }); + }, + leading: Text( + '封面', + style: leadingStyle, ), - ], - ), + trailing: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + if (_cover?.isNotEmpty == true) + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: LayoutBuilder( + builder: (context, constraints) { + return ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(6)), + child: CachedNetworkImage( + imageUrl: ImageUtil.thumbnailUrl(_cover!), + height: constraints.maxHeight, + width: constraints.maxHeight * 16 / 9, + fit: BoxFit.cover, + ), + ); + }, + ), + ), + Icon( + Icons.keyboard_arrow_right, + color: theme.colorScheme.outline, + ), + ], + ), + ); + }, ), const SizedBox(height: 16), ], @@ -368,26 +372,30 @@ class _CreateFavPageState extends State { ), const SizedBox(height: 16), ], - ListTile( - onTap: () => setState(() { - _isPublic = !_isPublic; - }), - tileColor: theme.colorScheme.onInverseSurface, - leading: Text( - '公开', - style: leadingStyle, - ), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - value: _isPublic, - onChanged: (value) { - setState(() { - _isPublic = value; - }); - }), - ), + Builder( + builder: (context) { + void onTap() { + _isPublic = !_isPublic; + (context as Element).markNeedsBuild(); + } + + return ListTile( + onTap: onTap, + tileColor: theme.colorScheme.onInverseSurface, + leading: Text( + '公开', + style: leadingStyle, + ), + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + value: _isPublic, + onChanged: (value) => onTap(), + ), + ), + ); + }, ), const SizedBox(height: 16), ], diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index fe24e152a..b3c440241 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -215,12 +215,12 @@ class _FavDetailPageState extends State { context: context, title: '确定删除该收藏夹?', onConfirm: () => - FavHttp.deleteFolder(mediaIds: [mediaId]).then((data) { - if (data['status']) { + FavHttp.deleteFolder(mediaIds: [mediaId]).then((res) { + if (res['status']) { SmartDialog.showToast('删除成功'); Get.back(result: true); } else { - SmartDialog.showToast(data['msg']); + SmartDialog.showToast(res['msg']); } }), ), diff --git a/lib/pages/fav_panel/view.dart b/lib/pages/fav_panel/view.dart index 81f55f303..b8b402a0c 100644 --- a/lib/pages/fav_panel/view.dart +++ b/lib/pages/fav_panel/view.dart @@ -52,26 +52,37 @@ class _FavPanelState extends State { FavFolderInfo item = widget.ctr.favFolderData.value.list![index]; return Material( type: MaterialType.transparency, - child: ListTile( - onTap: () => setState( - () => widget.ctr.onChoose(item.favState != 1, index)), - dense: true, - leading: FavUtil.isPublicFav(item.attr) - ? const Icon(Icons.folder_outlined) - : const Icon(Icons.lock_outline), - minLeadingWidth: 0, - title: Text(item.title), - subtitle: Text( - '${item.mediaCount}个内容 . ${FavUtil.isPublicFavText(item.attr)}', - ), - trailing: Transform.scale( - scale: 0.9, - child: Checkbox( - value: item.favState == 1, - onChanged: (bool? checkValue) => - setState(() => widget.ctr.onChoose(checkValue!, index)), - ), - ), + child: Builder( + builder: (context) { + void onTap() { + bool isChecked = item.favState == 1; + item + ..favState = isChecked ? 0 : 1 + ..mediaCount = + isChecked ? item.mediaCount - 1 : item.mediaCount + 1; + (context as Element).markNeedsBuild(); + } + + return ListTile( + onTap: onTap, + dense: true, + leading: FavUtil.isPublicFav(item.attr) + ? const Icon(Icons.folder_outlined) + : const Icon(Icons.lock_outline), + minLeadingWidth: 0, + title: Text(item.title), + subtitle: Text( + '${item.mediaCount}个内容 . ${FavUtil.isPublicFavText(item.attr)}', + ), + trailing: Transform.scale( + scale: 0.9, + child: Checkbox( + value: item.favState == 1, + onChanged: (bool? checkValue) => onTap(), + ), + ), + ); + }, ), ); }, @@ -136,7 +147,7 @@ class _FavPanelState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: () => Get.back(), + onPressed: Get.back, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), diff --git a/lib/pages/group_panel/view.dart b/lib/pages/group_panel/view.dart index 9cecc6796..f0a42f154 100644 --- a/lib/pages/group_panel/view.dart +++ b/lib/pages/group_panel/view.dart @@ -25,7 +25,7 @@ class GroupPanel extends StatefulWidget { class _GroupPanelState extends State { LoadingState> loadingState = LoadingState>.loading(); - bool showDefaultBtn = true; + RxBool showDefaultBtn = true.obs; @override void initState() { @@ -43,7 +43,7 @@ class _GroupPanelState extends State { ..map((item) { return item.checked = widget.tags?.contains(item.tagid) == true; }).toList(); - showDefaultBtn = !tagsList.any((e) => e.checked == true); + showDefaultBtn.value = !tagsList.any((e) => e.checked == true); loadingState = Success(tagsList); } else { loadingState = Error(res['msg']); @@ -89,28 +89,32 @@ class _GroupPanelState extends State { final item = response[index]; return Material( type: MaterialType.transparency, - child: ListTile( - onTap: () { - item.checked = !item.checked!; - showDefaultBtn = !response.any((e) => e.checked == true); - setState(() {}); + child: Builder( + builder: (context) { + void onTap() { + item.checked = !item.checked!; + (context as Element).markNeedsBuild(); + showDefaultBtn.value = + !response.any((e) => e.checked == true); + } + + return ListTile( + onTap: onTap, + dense: true, + leading: const Icon(Icons.group_outlined), + minLeadingWidth: 0, + title: Text(item.name ?? ''), + subtitle: + item.tip?.isNotEmpty == true ? Text(item.tip!) : null, + trailing: Transform.scale( + scale: 0.9, + child: Checkbox( + value: item.checked, + onChanged: (bool? checkValue) => onTap(), + ), + ), + ); }, - dense: true, - leading: const Icon(Icons.group_outlined), - minLeadingWidth: 0, - title: Text(item.name ?? ''), - subtitle: item.tip?.isNotEmpty == true ? Text(item.tip!) : null, - trailing: Transform.scale( - scale: 0.9, - child: Checkbox( - value: item.checked, - onChanged: (bool? checkValue) { - item.checked = checkValue; - showDefaultBtn = !response.any((e) => e.checked == true); - setState(() {}); - }, - ), - ), ), ); }, @@ -152,13 +156,13 @@ class _GroupPanelState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: () => onSave(), + onPressed: onSave, style: TextButton.styleFrom( padding: const EdgeInsets.only(left: 30, right: 30), foregroundColor: theme.colorScheme.onPrimary, backgroundColor: theme.colorScheme.primary, ), - child: Text(showDefaultBtn ? '保存至默认分组' : '保存'), + child: Obx(() => Text(showDefaultBtn.value ? '保存至默认分组' : '保存')), ), ], ), diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 5706b7e60..288ed9fd8 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -32,10 +32,14 @@ class _LoginPageState extends State { const SizedBox(height: 20), const Text('使用 bilibili 官方 App 扫码登录'), const SizedBox(height: 20), - Obx(() => Text('剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒', + Obx( + () => Text( + '剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒', style: TextStyle( fontFeatures: const [FontFeature.tabularFigures()], - color: theme.colorScheme.primaryFixedDim))), + color: theme.colorScheme.primaryFixedDim), + ), + ), const SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -107,29 +111,37 @@ class _LoginPageState extends State { }), ), const SizedBox(height: 10), - Obx(() => Text( - _loginPageCtr.statusQRCode.value, - style: TextStyle(color: theme.colorScheme.secondaryFixedDim), - )), - Obx(() => GestureDetector( - onTap: () => Utils.copyText( - _loginPageCtr.codeInfo['data']?['url'] ?? '', - toastText: '已复制到剪贴板,可粘贴至已登录的app私信处发送,然后点击已发送的链接打开'), - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 20), - child: Text(_loginPageCtr.codeInfo['data']?['url'] ?? "", - style: theme.textTheme.labelSmall!.copyWith( - color: theme.colorScheme.onSurface - .withValues(alpha: 0.4))), - ), - )), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text('请务必在 PiliPlus 开源仓库等可信渠道下载安装。', + Obx( + () => Text( + _loginPageCtr.statusQRCode.value, + style: TextStyle(color: theme.colorScheme.secondaryFixedDim), + ), + ), + Obx( + () => GestureDetector( + onTap: () => Utils.copyText( + _loginPageCtr.codeInfo['data']?['url'] ?? '', + toastText: '已复制到剪贴板,可粘贴至已登录的app私信处发送,然后点击已发送的链接打开'), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Text( + _loginPageCtr.codeInfo['data']?['url'] ?? "", style: theme.textTheme.labelSmall!.copyWith( - color: - theme.colorScheme.onSurface.withValues(alpha: 0.4)))), + color: theme.colorScheme.onSurface.withValues(alpha: 0.4), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + '请务必在 PiliPlus 开源仓库等可信渠道下载安装。', + style: theme.textTheme.labelSmall!.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.4), + ), + ), + ), ], ); } @@ -247,8 +259,9 @@ class _LoginPageState extends State { const EdgeInsets.fromLTRB(0.0, 2.0, 0.0, 16.0), children: [ const Padding( - padding: EdgeInsets.fromLTRB(25, 0, 25, 10), - child: Text("试试扫码、手机号登录,或选择")), + padding: EdgeInsets.fromLTRB(25, 0, 25, 10), + child: Text("试试扫码、手机号登录,或选择"), + ), ListTile( title: const Text( '找回密码(手机版)', @@ -303,20 +316,25 @@ class _LoginPageState extends State { ), const SizedBox(height: 20), Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - '根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n' - '盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n' - '账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n' - '请务必在 PiliPlus 开源仓库等可信渠道下载安装。', - textAlign: TextAlign.center, - style: theme.textTheme.labelSmall!.copyWith( - color: - theme.colorScheme.onSurface.withValues(alpha: 0.4)))), + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + '根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n' + '盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n' + '账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n' + '请务必在 PiliPlus 开源仓库等可信渠道下载安装。', + textAlign: TextAlign.center, + style: theme.textTheme.labelSmall!.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.4), + ), + ), + ), ], ); } + late final List> internationalDialingPrefix = + Constants.internationalDialingPrefix; + Widget loginBySmS(ThemeData theme) { return Column( children: [ @@ -324,55 +342,63 @@ class _LoginPageState extends State { const Text('使用手机短信验证码登录'), const SizedBox(height: 10), Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - child: DecoratedBox( - decoration: UnderlineTabIndicator( - borderSide: BorderSide( - color: theme.colorScheme.outline.withValues(alpha: 0.4)), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: DecoratedBox( + decoration: UnderlineTabIndicator( + borderSide: BorderSide( + color: theme.colorScheme.outline.withValues(alpha: 0.4), ), - child: Row( - children: [ - const SizedBox(width: 12), - Icon( - Icons.phone, - color: theme.colorScheme.onSurfaceVariant, + ), + child: Row( + children: [ + const SizedBox(width: 12), + Builder( + builder: (context) { + return PopupMenuButton>( + padding: EdgeInsets.zero, + tooltip: '选择国际冠码,' + '当前为${_loginPageCtr.selectedCountryCodeId['cname']},' + '+${_loginPageCtr.selectedCountryCodeId['country_id']}', + onSelected: (Map type) {}, + itemBuilder: (_) => internationalDialingPrefix + .map((Map item) { + return PopupMenuItem>( + onTap: () { + _loginPageCtr.selectedCountryCodeId = item; + (context as Element).markNeedsBuild(); + }, + value: item, + child: Row(children: [ + Text(item['cname']), + const Spacer(), + Text("+${item['country_id']}") + ]), + ); + }).toList(), + child: Row( + children: [ + Icon( + Icons.phone, + color: theme.colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 12), + Text( + "+${_loginPageCtr.selectedCountryCodeId['country_id']}"), + ], + ), + ); + }, + ), + const SizedBox(width: 6), + SizedBox( + height: 24, + child: VerticalDivider( + color: theme.colorScheme.outline.withValues(alpha: 0.5), ), - const SizedBox(width: 12), - PopupMenuButton>( - padding: EdgeInsets.zero, - tooltip: '选择国际冠码,' - '当前为${_loginPageCtr.selectedCountryCodeId['cname']},' - '+${_loginPageCtr.selectedCountryCodeId['country_id']}', - //position: PopupMenuPosition.under, - onSelected: (Map type) {}, - itemBuilder: (BuildContext context) => Constants - .internationalDialingPrefix - .map((Map item) { - return PopupMenuItem>( - onTap: () => setState(() { - _loginPageCtr.selectedCountryCodeId = item; - }), - value: item, - child: Row(children: [ - Text(item['cname']), - const Spacer(), - Text("+${item['country_id']}") - ]), - ); - }).toList(), - child: Text( - "+${_loginPageCtr.selectedCountryCodeId['country_id']}"), - ), - const SizedBox(width: 6), - SizedBox( - height: 24, - child: VerticalDivider( - color: theme.colorScheme.outline.withValues(alpha: 0.5), - ), - ), - const SizedBox(width: 6), - Expanded( - child: TextField( + ), + const SizedBox(width: 6), + Expanded( + child: TextField( controller: _loginPageCtr.telTextController, keyboardType: TextInputType.number, inputFormatters: [ @@ -386,45 +412,51 @@ class _LoginPageState extends State { icon: const Icon(Icons.clear), ), ), - )), - ], - ), - )), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - child: DecoratedBox( - decoration: UnderlineTabIndicator( - borderSide: BorderSide( - color: theme.colorScheme.outline.withValues(alpha: 0.4)), - ), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _loginPageCtr.smsCodeTextController, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.sms_outlined), - border: InputBorder.none, - labelText: '验证码', - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ), ), - Obx(() => TextButton.icon( - onPressed: _loginPageCtr.smsSendCooldown > 0 - ? null - : _loginPageCtr.sendSmsCode, - icon: const Icon(Icons.send), - label: Text(_loginPageCtr.smsSendCooldown > 0 - ? '等待${_loginPageCtr.smsSendCooldown}秒' - : '获取验证码'), - )), - ], + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: DecoratedBox( + decoration: UnderlineTabIndicator( + borderSide: BorderSide( + color: theme.colorScheme.outline.withValues(alpha: 0.4), ), - )), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _loginPageCtr.smsCodeTextController, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.sms_outlined), + border: InputBorder.none, + labelText: '验证码', + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ), + ), + Obx( + () => TextButton.icon( + onPressed: _loginPageCtr.smsSendCooldown > 0 + ? null + : _loginPageCtr.sendSmsCode, + icon: const Icon(Icons.send), + label: Text(_loginPageCtr.smsSendCooldown > 0 + ? '等待${_loginPageCtr.smsSendCooldown}秒' + : '获取验证码'), + ), + ), + ], + ), + ), + ), const SizedBox(height: 20), OutlinedButton.icon( onPressed: _loginPageCtr.loginBySmsCode, @@ -433,15 +465,17 @@ class _LoginPageState extends State { ), const SizedBox(height: 20), Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - '手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n' - '本地仅存储登录凭证。\n' - '请务必在 PiliPlus 开源仓库等可信渠道下载安装。', - textAlign: TextAlign.center, - style: theme.textTheme.labelSmall!.copyWith( - color: - theme.colorScheme.onSurface.withValues(alpha: 0.4)))), + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + '手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n' + '本地仅存储登录凭证。\n' + '请务必在 PiliPlus 开源仓库等可信渠道下载安装。', + textAlign: TextAlign.center, + style: theme.textTheme.labelSmall!.copyWith( + color: theme.colorScheme.onSurface.withValues(alpha: 0.4), + ), + ), + ), ], ); } diff --git a/lib/pages/match_info/view.dart b/lib/pages/match_info/view.dart index 103bbb6bf..7497bd937 100644 --- a/lib/pages/match_info/view.dart +++ b/lib/pages/match_info/view.dart @@ -292,7 +292,7 @@ class _MatchInfoPageState extends State { SizedBox( height: 35, child: TextButton.icon( - onPressed: () => _controller.queryBySort(), + onPressed: _controller.queryBySort, icon: Icon( Icons.sort, size: 16, diff --git a/lib/pages/mine/controller.dart b/lib/pages/mine/controller.dart index 3393cc758..156bd2d19 100644 --- a/lib/pages/mine/controller.dart +++ b/lib/pages/mine/controller.dart @@ -83,7 +83,7 @@ class MineController extends GetxController { } } - static void onChangeAnonymity(BuildContext context) { + static void onChangeAnonymity() { if (Accounts.account.isEmpty) { SmartDialog.showToast('请先登录'); return; diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index b03ade651..053081c02 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -33,20 +33,21 @@ class _MinePageState extends State { style: theme.textTheme.titleMedium, ), const Spacer(), - IconButton( - iconSize: 40.0, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - tooltip: "${MineController.anonymity.value ? '退出' : '进入'}无痕模式", - onPressed: () { - MineController.onChangeAnonymity(context); - setState(() {}); + Obx( + () { + return IconButton( + iconSize: 40.0, + padding: const EdgeInsets.all(8), + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + tooltip: "${MineController.anonymity.value ? '退出' : '进入'}无痕模式", + onPressed: MineController.onChangeAnonymity, + icon: MineController.anonymity.value + ? const Icon(MdiIcons.incognito, size: 24) + : const Icon(MdiIcons.incognitoOff, size: 24), + ); }, - icon: MineController.anonymity.value - ? const Icon(MdiIcons.incognito, size: 24) - : const Icon(MdiIcons.incognitoOff, size: 24), ), Obx( () { diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 6600ffdcb..50d433a54 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -20,31 +20,31 @@ class _PlaySpeedPageState extends State { late double longPressSpeedDefault; late List speedList; late bool enableAutoLongPressSpeed; - List> sheetMenu = [ - { - 'id': 1, - 'title': '设置为默认倍速', - 'leading': const Icon( + List<({int id, String title, Icon icon})> sheetMenu = [ + ( + id: 1, + title: '设置为默认倍速', + icon: const Icon( Icons.speed, size: 21, ), - }, - { - 'id': 2, - 'title': '设置为默认长按倍速', - 'leading': const Icon( + ), + ( + id: 2, + title: '设置为默认长按倍速', + icon: const Icon( Icons.speed_sharp, size: 21, ), - }, - { - 'id': -1, - 'title': '删除该项', - 'leading': const Icon( + ), + ( + id: -1, + title: '删除该项', + icon: const Icon( Icons.delete_outline, size: 21, ), - }, + ), ]; Box get video => GStorage.video; @@ -62,13 +62,6 @@ class _PlaySpeedPageState extends State { speedList = GStorage.speedList; enableAutoLongPressSpeed = GStorage.setting .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); - if (enableAutoLongPressSpeed) { - Map newItem = sheetMenu[1]; - newItem['show'] = false; - setState(() { - sheetMenu[1] = newItem; - }); - } } // 添加自定义倍速 @@ -147,16 +140,18 @@ class _PlaySpeedPageState extends State { const SizedBox(height: 10), ...sheetMenu.map( (item) => ListTile( + enabled: + enableAutoLongPressSpeed && item.id == 2 ? false : true, onTap: () { - Navigator.pop(context); - menuAction(index, item['id']); + Get.back(); + menuAction(index, item.id); }, minLeadingWidth: 0, iconColor: theme.colorScheme.onSurface, - leading: item['leading'], + leading: item.icon, title: Text( - item['title'], - style: theme.textTheme.titleSmall, + item.title, + style: const TextStyle(fontSize: 14), ), ), ), @@ -233,14 +228,8 @@ class _PlaySpeedPageState extends State { subtitle: '根据默认倍速长按时自动双倍', setKey: SettingBoxKey.enableAutoLongPressSpeed, defaultVal: enableAutoLongPressSpeed, - onChanged: (val) { - Map newItem = sheetMenu[1]; - val ? newItem['show'] = false : newItem['show'] = true; - setState(() { - sheetMenu[1] = newItem; - enableAutoLongPressSpeed = val; - }); - }, + onChanged: (val) => + setState(() => enableAutoLongPressSpeed = val), ), if (!enableAutoLongPressSpeed) ListTile( diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 324d212e3..4022bf996 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -1480,7 +1480,7 @@ List get privacySettings => [ SettingsModel( settingsType: SettingsType.normal, onTap: (setState) { - MineController.onChangeAnonymity(Get.context!); + MineController.onChangeAnonymity(); setState(); }, leading: const Icon(Icons.privacy_tip_outlined), diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index 8d6aa35d4..2bcc6a091 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -74,59 +74,63 @@ class _SponsorBlockPageState extends State { Widget _blockLimitItem( ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) => - ListTile( - dense: true, - onTap: () { - _textController.text = _blockLimit.toString(); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('最短片段时长', style: titleStyle), - content: TextFormField( - keyboardType: - const TextInputType.numberWithOptions(decimal: true), - controller: _textController, - autofocus: true, - decoration: const InputDecoration(suffixText: 's'), - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), - ], - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: theme.colorScheme.outline, - ), + Builder( + builder: (context) { + return ListTile( + dense: true, + onTap: () { + _textController.text = _blockLimit.toString(); + showDialog( + context: context, + builder: (_) { + return AlertDialog( + title: Text('最短片段时长', style: titleStyle), + content: TextFormField( + keyboardType: + const TextInputType.numberWithOptions(decimal: true), + controller: _textController, + autofocus: true, + decoration: const InputDecoration(suffixText: 's'), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), + ], ), - ), - TextButton( - onPressed: () { - Get.back(); - _blockLimit = max( - 0.0, double.tryParse(_textController.text) ?? 0.0); - setting.put(SettingBoxKey.blockLimit, _blockLimit); - setState(() {}); - }, - child: const Text('确定'), - ) - ], + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: theme.colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + _blockLimit = max(0.0, + double.tryParse(_textController.text) ?? 0.0); + setting.put(SettingBoxKey.blockLimit, _blockLimit); + (context as Element).markNeedsBuild(); + }, + child: const Text('确定'), + ) + ], + ); + }, ); }, + title: Text('最短片段时长', style: titleStyle), + subtitle: Text( + '忽略短于此时长的片段', + style: subTitleStyle, + ), + trailing: Text( + '${_blockLimit}s', + style: const TextStyle(fontSize: 13), + ), ); }, - title: Text('最短片段时长', style: titleStyle), - subtitle: Text( - '忽略短于此时长的片段', - style: subTitleStyle, - ), - trailing: Text( - '${_blockLimit}s', - style: const TextStyle(fontSize: 13), - ), ); Widget _aboudItem(TextStyle titleStyle, TextStyle subTitleStyle) => ListTile( @@ -138,230 +142,250 @@ class _SponsorBlockPageState extends State { Widget _userIdItem( ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) => - ListTile( - dense: true, - title: Text('用户ID', style: titleStyle), - subtitle: Text(_userId, style: subTitleStyle), - onTap: () { - final key = GlobalKey(); - _textController.text = _userId; - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('用户ID', style: titleStyle), - content: Form( - key: key, - child: TextFormField( - minLines: 1, - maxLines: 4, - autofocus: true, - controller: _textController, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\d]+')), - ], - validator: (value) { - if ((value?.length ?? -1) < 30) { - return '用户ID要求至少为30个字符长度的纯字符串'; - } - return null; - }, - ), - ), - actions: [ - TextButton( - onPressed: () { - Get.back(); - _userId = const Uuid().v4().replaceAll('-', ''); - setting.put(SettingBoxKey.blockUserID, _userId); - setState(() {}); - }, - child: const Text('随机'), - ), - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: theme.colorScheme.outline, + Builder( + builder: (context) { + return ListTile( + dense: true, + title: Text('用户ID', style: titleStyle), + subtitle: Text(_userId, style: subTitleStyle), + onTap: () { + final key = GlobalKey(); + _textController.text = _userId; + showDialog( + context: context, + builder: (_) { + return AlertDialog( + title: Text('用户ID', style: titleStyle), + content: Form( + key: key, + child: TextFormField( + minLines: 1, + maxLines: 4, + autofocus: true, + controller: _textController, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'[a-zA-Z\d]+')), + ], + validator: (value) { + if ((value?.length ?? -1) < 30) { + return '用户ID要求至少为30个字符长度的纯字符串'; + } + return null; + }, ), ), - ), - TextButton( - onPressed: () { - if (key.currentState?.validate() == true) { - Get.back(); - _userId = _textController.text; - setting.put(SettingBoxKey.blockUserID, _userId); - setState(() {}); - } - }, - child: const Text('确定'), - ) - ], + actions: [ + TextButton( + onPressed: () { + Get.back(); + _userId = const Uuid().v4().replaceAll('-', ''); + setting.put(SettingBoxKey.blockUserID, _userId); + (context as Element).markNeedsBuild(); + }, + child: const Text('随机'), + ), + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: theme.colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + if (key.currentState?.validate() == true) { + Get.back(); + _userId = _textController.text; + setting.put(SettingBoxKey.blockUserID, _userId); + (context as Element).markNeedsBuild(); + } + }, + child: const Text('确定'), + ) + ], + ); + }, ); }, ); }, ); - void _updateBlockToast() { - _blockToast = !_blockToast; - setting.put(SettingBoxKey.blockToast, _blockToast); - setState(() {}); - } + Widget _blockToastItem(TextStyle titleStyle) => Builder( + builder: (context) { + void update() { + _blockToast = !_blockToast; + setting.put(SettingBoxKey.blockToast, _blockToast); + (context as Element).markNeedsBuild(); + } - void _updateBlockTrack() { - _blockTrack = !_blockTrack; - setting.put(SettingBoxKey.blockTrack, _blockTrack); - setState(() {}); - } - - Widget _blockToastItem(TextStyle titleStyle) => ListTile( - dense: true, - onTap: _updateBlockToast, - title: Text( - '显示跳过Toast', - style: titleStyle, - ), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith((states) { - if (states.isNotEmpty && states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; - }), - value: _blockToast, - onChanged: (val) { - _updateBlockToast(); - }, - ), - )); + return ListTile( + dense: true, + onTap: update, + title: Text( + '显示跳过Toast', + style: titleStyle, + ), + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: _blockToast, + onChanged: (val) => update(), + ), + ), + ); + }, + ); Widget _blockTrackItem(TextStyle titleStyle, TextStyle subTitleStyle) => - ListTile( - dense: true, - onTap: _updateBlockTrack, - title: Text( - '跳过次数统计跟踪', - style: titleStyle, - ), - subtitle: Text( - // from origin extension - '此功能追踪您跳过了哪些片段,让用户知道他们提交的片段帮助了多少人。同时点赞会作为依据,确保垃圾信息不会污染数据库。在您每次跳过片段时,我们都会向服务器发送一条消息。希望大家开启此项设置,以便得到更准确的统计数据。:)', - style: subTitleStyle, - ), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith((states) { - if (states.isNotEmpty && states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; - }), - value: _blockTrack, - onChanged: (val) { - _updateBlockTrack(); - }, - ), - )); + Builder( + builder: (context) { + void update() { + _blockTrack = !_blockTrack; + setting.put(SettingBoxKey.blockTrack, _blockTrack); + (context as Element).markNeedsBuild(); + } + + return ListTile( + dense: true, + onTap: update, + title: Text( + '跳过次数统计跟踪', + style: titleStyle, + ), + subtitle: Text( + // from origin extension + '此功能追踪您跳过了哪些片段,让用户知道他们提交的片段帮助了多少人。同时点赞会作为依据,确保垃圾信息不会污染数据库。在您每次跳过片段时,我们都会向服务器发送一条消息。希望大家开启此项设置,以便得到更准确的统计数据。:)', + style: subTitleStyle, + ), + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: _blockTrack, + onChanged: (val) => update(), + ), + )); + }, + ); Widget _blockServerItem( ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) => - ListTile( - dense: true, - onTap: () { - _textController.text = _blockServer; - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('服务器地址', style: titleStyle), - content: TextFormField( - keyboardType: TextInputType.url, - controller: _textController, - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () { - Get.back(); - _blockServer = HttpString.sponsorBlockBaseUrl; - setting.put(SettingBoxKey.blockServer, _blockServer); - Request.accountManager.blockServer = _blockServer; - setState(() {}); - }, - child: const Text('重置'), - ), - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: theme.colorScheme.outline, - ), + Builder( + builder: (context) { + return ListTile( + dense: true, + onTap: () { + _textController.text = _blockServer; + showDialog( + context: context, + builder: (_) { + return AlertDialog( + title: Text('服务器地址', style: titleStyle), + content: TextFormField( + keyboardType: TextInputType.url, + controller: _textController, + autofocus: true, ), - ), - TextButton( - onPressed: () { - Get.back(); - _blockServer = _textController.text; - setting.put(SettingBoxKey.blockServer, _blockServer); - Request.accountManager.blockServer = _blockServer; - setState(() {}); - }, - child: const Text('确定'), - ) - ], + actions: [ + TextButton( + onPressed: () { + Get.back(); + _blockServer = HttpString.sponsorBlockBaseUrl; + setting.put(SettingBoxKey.blockServer, _blockServer); + Request.accountManager.blockServer = _blockServer; + (context as Element).markNeedsBuild(); + }, + child: const Text('重置'), + ), + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: theme.colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + _blockServer = _textController.text; + setting.put(SettingBoxKey.blockServer, _blockServer); + Request.accountManager.blockServer = _blockServer; + (context as Element).markNeedsBuild(); + }, + child: const Text('确定'), + ) + ], + ); + }, ); }, + title: Text( + '服务器地址', + style: titleStyle, + ), + subtitle: Text( + _blockServer, + style: subTitleStyle, + ), ); }, - title: Text( - '服务器地址', - style: titleStyle, - ), - subtitle: Text( - _blockServer, - style: subTitleStyle, - ), ); - Widget _serverStatusItem(ThemeData theme, TextStyle titleStyle) => ListTile( - dense: true, - onTap: () { - setState(() { - _serverStatus = null; - }); - _checkServerStatus(); + Widget _serverStatusItem(ThemeData theme, TextStyle titleStyle) => Builder( + builder: (context) { + return ListTile( + dense: true, + onTap: () { + _serverStatus = null; + (context as Element).markNeedsBuild(); + _checkServerStatus(); + }, + title: Text('服务器状态', style: titleStyle), + trailing: Text( + _serverStatus == null + ? '——' + : _serverStatus == true + ? '正常' + : '错误', + style: TextStyle( + fontSize: 13, + color: _serverStatus == null + ? null + : _serverStatus == true + ? theme.colorScheme.primary + : theme.colorScheme.error, + ), + ), + ); }, - title: Text('服务器状态', style: titleStyle), - trailing: Text( - _serverStatus == null - ? '——' - : _serverStatus == true - ? '正常' - : '错误', - style: TextStyle( - fontSize: 13, - color: _serverStatus == null - ? null - : _serverStatus == true - ? theme.colorScheme.primary - : theme.colorScheme.error, - ), - ), ); - void onSelectColor(int index) { + void onSelectColor(BuildContext context, int index, Color color, + Pair item) { showDialog( context: context, - builder: (context) => AlertDialog( + builder: (_) => AlertDialog( clipBehavior: Clip.hardEdge, contentPadding: const EdgeInsets.symmetric(vertical: 16), title: Text.rich( @@ -378,28 +402,28 @@ class _SponsorBlockPageState extends State { width: 10, decoration: BoxDecoration( shape: BoxShape.circle, - color: _blockColor[index], + color: color, ), ), style: const TextStyle(fontSize: 13, height: 1), ), TextSpan( - text: ' ${_blockSettings[index].first.title}', + text: ' ${item.first.title}', style: const TextStyle(fontSize: 13, height: 1), ), ], ), ), content: SlideColorPicker( - color: _blockColor[index], + color: color, callback: (Color? color) { - _blockColor[index] = color ?? _blockSettings[index].first.color; + _blockColor[index] = color ?? item.first.color; setting.put( SettingBoxKey.blockColor, _blockColor .map((item) => item.value.toRadixString(16).substring(2)) .toList()); - setState(() {}); + (context as Element).markNeedsBuild(); }, ), ), @@ -447,95 +471,8 @@ class _SponsorBlockPageState extends State { dividerL, SliverList.separated( itemCount: _blockSettings.length, - itemBuilder: (context, index) => ListTile( - dense: true, - enabled: _blockSettings[index].second != SkipType.disable, - onTap: () => onSelectColor(index), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Container( - height: 10, - width: 10, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _blockColor[index], - ), - ), - style: const TextStyle(fontSize: 14, height: 1), - ), - TextSpan( - text: ' ${_blockSettings[index].first.title}', - style: const TextStyle(fontSize: 14, height: 1), - ), - ], - ), - ), - PopupMenuButton( - initialValue: _blockSettings[index].second, - onSelected: (item) { - _blockSettings[index].second = item; - setting.put( - SettingBoxKey.blockSettings, - _blockSettings - .map((item) => item.second.index) - .toList()); - setState(() {}); - }, - itemBuilder: (context) => SkipType.values - .map((item) => PopupMenuItem( - value: item, - child: Text(item.title), - )) - .toList(), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _blockSettings[index].second.title, - style: TextStyle( - height: 1, - fontSize: 14, - color: _blockSettings[index].second == - SkipType.disable - ? theme.colorScheme.outline - .withValues(alpha: 0.7) - : theme.colorScheme.secondary, - ), - strutStyle: const StrutStyle(height: 1, leading: 0), - ), - Icon( - MdiIcons.unfoldMoreHorizontal, - size: MediaQuery.textScalerOf(context).scale(14), - color: - _blockSettings[index].second == SkipType.disable - ? theme.colorScheme.outline - .withValues(alpha: 0.7) - : theme.colorScheme.secondary, - ), - ], - ), - ), - ), - ], - ), - subtitle: Text( - _blockSettings[index].first.description, - style: TextStyle( - fontSize: 12, - color: _blockSettings[index].second == SkipType.disable - ? null - : theme.colorScheme.outline, - ), - ), - ), + itemBuilder: (context, index) => + _buildItem(theme, index, _blockSettings[index]), separatorBuilder: (context, index) => divider, ), dividerL, @@ -555,4 +492,101 @@ class _SponsorBlockPageState extends State { ), ); } + + Widget _buildItem( + ThemeData theme, int index, Pair item) { + return Builder( + builder: (context) { + Color color = _blockColor[index]; + return ListTile( + dense: true, + enabled: item.second != SkipType.disable, + onTap: () => onSelectColor(context, index, color, item), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Container( + height: 10, + width: 10, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + style: const TextStyle(fontSize: 14, height: 1), + ), + TextSpan( + text: ' ${item.first.title}', + style: const TextStyle(fontSize: 14, height: 1), + ), + ], + ), + ), + Builder( + builder: (context) { + return PopupMenuButton( + initialValue: item.second, + onSelected: (e) { + item.second = e; + setting.put(SettingBoxKey.blockSettings, + _blockSettings.map((e) => e.second.index).toList()); + (context as Element).markNeedsBuild(); + }, + itemBuilder: (context) => SkipType.values + .map((item) => PopupMenuItem( + value: item, + child: Text(item.title), + )) + .toList(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + item.second.title, + style: TextStyle( + height: 1, + fontSize: 14, + color: item.second == SkipType.disable + ? theme.colorScheme.outline + .withValues(alpha: 0.7) + : theme.colorScheme.secondary, + ), + strutStyle: const StrutStyle(height: 1, leading: 0), + ), + Icon( + MdiIcons.unfoldMoreHorizontal, + size: MediaQuery.textScalerOf(context).scale(14), + color: item.second == SkipType.disable + ? theme.colorScheme.outline + .withValues(alpha: 0.7) + : theme.colorScheme.secondary, + ), + ], + ), + ), + ); + }, + ), + ], + ), + subtitle: Text( + item.first.description, + style: TextStyle( + fontSize: 12, + color: item.second == SkipType.disable + ? null + : theme.colorScheme.outline, + ), + ), + ); + }, + ); + } } diff --git a/lib/pages/video/introduction/ugc/widgets/action_item.dart b/lib/pages/video/introduction/ugc/widgets/action_item.dart index 2fa09cdb5..af9c0101b 100644 --- a/lib/pages/video/introduction/ugc/widgets/action_item.dart +++ b/lib/pages/video/introduction/ugc/widgets/action_item.dart @@ -47,7 +47,6 @@ class ActionItemState extends State late final _isThumbsUp = widget.semanticsLabel == '点赞'; late int _lastTime; - late bool _hideCircle = false; Timer? _timer; void _startLongPress() { @@ -100,15 +99,12 @@ class ActionItemState extends State } void listener() { - setState(() { - _hideCircle = controller?.value == 1; - if (_hideCircle) { - controller?.reset(); - if (_isThumbsUp) { - widget.onLongPress?.call(); - } + if (controller!.value == 1) { + controller!.reset(); + if (_isThumbsUp) { + widget.onLongPress?.call(); } - }); + } } void cancelTimer() { @@ -157,12 +153,15 @@ class ActionItemState extends State clipBehavior: Clip.none, alignment: Alignment.center, children: [ - if (widget.needAnim && !_hideCircle) - CustomPaint( - size: const Size(28, 28), - painter: _ArcPainter( - color: theme.colorScheme.primary, - sweepAngle: _animation!.value, + if (widget.needAnim) + AnimatedBuilder( + animation: _animation!, + builder: (context, child) => CustomPaint( + size: const Size(28, 28), + painter: _ArcPainter( + color: theme.colorScheme.primary, + sweepAngle: _animation!.value, + ), ), ) else diff --git a/lib/pages/video/pay_coins/view.dart b/lib/pages/video/pay_coins/view.dart index 92abe88ba..3080f4b05 100644 --- a/lib/pages/video/pay_coins/view.dart +++ b/lib/pages/video/pay_coins/view.dart @@ -267,9 +267,7 @@ class _PayCoinsPageState extends State physics: const ClampingScrollPhysics(), itemCount: widget.copyright == 1 ? 2 : 1, controller: _controller, - onPageChanged: (index) => setState(() { - _scale(); - }), + onPageChanged: (index) => setState(_scale), itemBuilder: (context, index) { return ListenableBuilder( listenable: _controller, diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 587562bd1..df85ac871 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -131,23 +131,33 @@ class _PostPanelState extends CommonCollapseSlidePageState { runSpacing: 8, spacing: 16, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: segmentWidget( - theme, - isFirst: true, - index: index, - ), + Builder( + builder: (context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: segmentWidget( + context, + theme, + isFirst: true, + index: index, + ), + ); + }, ), if (list![index].category != SegmentType.poi_highlight) - Row( - mainAxisSize: MainAxisSize.min, - children: segmentWidget( - theme, - isFirst: false, - index: index, - ), + Builder( + builder: (context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: segmentWidget( + context, + theme, + isFirst: false, + index: index, + ), + ); + }, ), ], ), @@ -161,72 +171,86 @@ class _PostPanelState extends CommonCollapseSlidePageState { mainAxisSize: MainAxisSize.min, children: [ const Text('分类: '), - PopupMenuButton( - initialValue: list![index].category, - onSelected: (item) { - list![index].category = item; - List constraintList = - item.toActionType; - if (!constraintList.contains( - list![index].actionType)) { - list![index].actionType = - constraintList.first; - } - switch (item) { - case SegmentType.poi_highlight: - updateSegment( - isFirst: false, - index: index, - value: list![index] - .segment - .first, - ); - break; - case SegmentType.exclusive_access: - updateSegment( - isFirst: true, - index: index, - value: 0, - ); - break; - case _: - } - setState(() {}); + Builder( + builder: (context) { + return PopupMenuButton( + initialValue: + list![index].category, + onSelected: (item) { + list![index].category = item; + List + constraintList = + item.toActionType; + if (!constraintList.contains( + list![index].actionType)) { + list![index].actionType = + constraintList.first; + } + switch (item) { + case SegmentType + .poi_highlight: + updateSegment( + isFirst: false, + index: index, + value: list![index] + .segment + .first, + ); + break; + case SegmentType + .exclusive_access: + updateSegment( + isFirst: true, + index: index, + value: 0, + ); + break; + default: + } + (context as Element) + .markNeedsBuild(); + }, + itemBuilder: (context) => + SegmentType.values + .map((item) => + PopupMenuItem< + SegmentType>( + value: item, + child: Text( + item.title), + )) + .toList(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + list![index].category.title, + style: TextStyle( + height: 1, + fontSize: 14, + color: theme.colorScheme + .secondary, + ), + strutStyle: + const StrutStyle( + height: 1, + leading: 0, + ), + ), + Icon( + MdiIcons + .unfoldMoreHorizontal, + size: + MediaQuery.textScalerOf( + context) + .scale(14), + color: theme + .colorScheme.secondary, + ), + ], + ), + ); }, - itemBuilder: (context) => SegmentType - .values - .map((item) => - PopupMenuItem( - value: item, - child: Text(item.title), - )) - .toList(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - list![index].category.title, - style: TextStyle( - height: 1, - fontSize: 14, - color: theme - .colorScheme.secondary, - ), - strutStyle: const StrutStyle( - height: 1, - leading: 0, - ), - ), - Icon( - MdiIcons.unfoldMoreHorizontal, - size: MediaQuery.textScalerOf( - context) - .scale(14), - color: - theme.colorScheme.secondary, - ), - ], - ), ), ], ), @@ -234,60 +258,64 @@ class _PostPanelState extends CommonCollapseSlidePageState { mainAxisSize: MainAxisSize.min, children: [ const Text('行为类别: '), - PopupMenuButton( - initialValue: list![index].actionType, - onSelected: (item) { - list![index].actionType = item; - if (item == ActionType.full) { - updateSegment( - isFirst: true, - index: index, - value: 0, - ); - } - setState(() {}); - }, - itemBuilder: (context) => ActionType - .values - .map( - (item) => - PopupMenuItem( - enabled: list![index] - .category - .toActionType - .contains(item), - value: item, - child: Text(item.title), + Builder(builder: (context) { + return PopupMenuButton( + initialValue: + list![index].actionType, + onSelected: (item) { + list![index].actionType = item; + if (item == ActionType.full) { + updateSegment( + isFirst: true, + index: index, + value: 0, + ); + } + (context as Element) + .markNeedsBuild(); + }, + itemBuilder: (context) => ActionType + .values + .map( + (item) => + PopupMenuItem( + enabled: list![index] + .category + .toActionType + .contains(item), + value: item, + child: Text(item.title), + ), + ) + .toList(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + list![index].actionType.title, + style: TextStyle( + height: 1, + fontSize: 14, + color: theme + .colorScheme.secondary, + ), + strutStyle: const StrutStyle( + height: 1, + leading: 0, + ), ), - ) - .toList(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - list![index].actionType.title, - style: TextStyle( - height: 1, - fontSize: 14, + Icon( + MdiIcons.unfoldMoreHorizontal, + size: MediaQuery.textScalerOf( + context) + .scale(14), color: theme .colorScheme.secondary, ), - strutStyle: const StrutStyle( - height: 1, - leading: 0, - ), - ), - Icon( - MdiIcons.unfoldMoreHorizontal, - size: MediaQuery.textScalerOf( - context) - .scale(14), - color: - theme.colorScheme.secondary, - ), - ], - ), - ), + ], + ), + ); + }), ], ), ], @@ -419,6 +447,7 @@ class _PostPanelState extends CommonCollapseSlidePageState { } List segmentWidget( + BuildContext context, ThemeData theme, { required int index, required bool isFirst, @@ -436,13 +465,12 @@ class _PostPanelState extends CommonCollapseSlidePageState { tooltip: '设为当前', icon: Icons.my_location, onPressed: () { - setState(() { - updateSegment( - isFirst: isFirst, - index: index, - value: currentPos, - ); - }); + updateSegment( + isFirst: isFirst, + index: index, + value: currentPos, + ); + (context as Element).markNeedsBuild(); }, ), const SizedBox(width: 5), @@ -452,13 +480,12 @@ class _PostPanelState extends CommonCollapseSlidePageState { tooltip: isFirst ? '视频开头' : '视频结尾', icon: isFirst ? Icons.first_page : Icons.last_page, onPressed: () { - setState(() { - updateSegment( - isFirst: isFirst, - index: index, - value: isFirst ? 0 : videoDuration, - ); - }); + updateSegment( + isFirst: isFirst, + index: index, + value: isFirst ? 0 : videoDuration, + ); + (context as Element).markNeedsBuild(); }, ), const SizedBox(width: 5), @@ -508,13 +535,12 @@ class _PostPanelState extends CommonCollapseSlidePageState { duration += split[i] * pow(60, i); } if (duration <= videoDuration) { - setState(() { - updateSegment( - isFirst: isFirst, - index: index, - value: duration, - ); - }); + updateSegment( + isFirst: isFirst, + index: index, + value: duration, + ); + (context as Element).markNeedsBuild(); } } catch (e) { if (kDebugMode) debugPrint(e.toString()); diff --git a/lib/plugin/pl_player/widgets/play_pause_btn.dart b/lib/plugin/pl_player/widgets/play_pause_btn.dart index 64ef3c7c4..ccc5105d6 100644 --- a/lib/plugin/pl_player/widgets/play_pause_btn.dart +++ b/lib/plugin/pl_player/widgets/play_pause_btn.dart @@ -22,42 +22,32 @@ class PlayOrPauseButton extends StatefulWidget { class PlayOrPauseButtonState extends State with SingleTickerProviderStateMixin { - late final AnimationController animation; - - StreamSubscription? subscription; + late final AnimationController controller; + late final StreamSubscription subscription; late Player player; - bool isOpacity = false; - - PlPlayerController get plPlayerController => widget.plPlayerController; @override void initState() { super.initState(); - player = plPlayerController.videoPlayerController!; - animation = AnimationController( + player = widget.plPlayerController.videoPlayerController!; + controller = AnimationController( vsync: this, value: player.state.playing ? 1 : 0, duration: const Duration(milliseconds: 200), ); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - subscription ??= player.stream.playing.listen((event) { - if (event) { - animation.forward().whenComplete(() => {isOpacity = true}); + subscription = player.stream.playing.listen((playing) { + if (playing) { + controller.forward(); } else { - animation.reverse().whenComplete(() => {isOpacity = false}); + controller.reverse(); } - setState(() {}); }); } @override void dispose() { - animation.dispose(); - subscription?.cancel(); + subscription.cancel(); + controller.dispose(); super.dispose(); } @@ -67,6 +57,7 @@ class PlayOrPauseButtonState extends State width: 42, height: 34, child: GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () async { if (player.state.completed) { await player.seek(Duration.zero); @@ -77,11 +68,8 @@ class PlayOrPauseButtonState extends State }, child: Center( child: AnimatedIcon( - semanticLabel: - plPlayerController.videoPlayerController!.state.playing - ? '暂停' - : '播放', - progress: animation, + semanticLabel: player.state.playing ? '暂停' : '播放', + progress: controller, icon: AnimatedIcons.play_pause, color: Colors.white, size: 20, diff --git a/lib/services/shutdown_timer_service.dart b/lib/services/shutdown_timer_service.dart index 3a31e1a38..f642b2e18 100644 --- a/lib/services/shutdown_timer_service.dart +++ b/lib/services/shutdown_timer_service.dart @@ -45,8 +45,8 @@ class ShutdownTimerService with WidgetsBindingObserver { return; } SmartDialog.showToast("设置 $scheduledExitInMinutes 分钟后定时关闭"); - _shutdownTimer = Timer( - Duration(minutes: scheduledExitInMinutes), () => _shutdownDecider()); + _shutdownTimer = + Timer(Duration(minutes: scheduledExitInMinutes), _shutdownDecider); } void _showTimeUpButPauseDialog() { diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index e33b70fec..c680b4edf 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -119,8 +119,9 @@ class PageUtils { onPressed: () { Get.back(); int choice = int.tryParse(duration) ?? 0; - shutdownTimerService.scheduledExitInMinutes = choice; - shutdownTimerService.startShutdownTimer(); + shutdownTimerService + ..scheduledExitInMinutes = choice + ..startShutdownTimer(); setState(() {}); }, child: const Text('确定'), @@ -181,59 +182,71 @@ class PageUtils { ), ), if (!isLive) ...[ - ListTile( - dense: true, - onTap: () { - shutdownTimerService.waitForPlayingCompleted = - !shutdownTimerService.waitForPlayingCompleted; - setState(() {}); + Builder( + builder: (context) { + return ListTile( + dense: true, + onTap: () { + shutdownTimerService.waitForPlayingCompleted = + !shutdownTimerService.waitForPlayingCompleted; + (context as Element).markNeedsBuild(); + }, + title: const Text("额外等待视频播放完毕", style: titleStyle), + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + thumbIcon: + WidgetStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: shutdownTimerService + .waitForPlayingCompleted, + onChanged: (value) { + shutdownTimerService.waitForPlayingCompleted = + value; + (context as Element).markNeedsBuild(); + }, + ), + ), + ); }, - title: const Text("额外等待视频播放完毕", style: titleStyle), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; - }), - value: shutdownTimerService.waitForPlayingCompleted, - onChanged: (value) => setState(() => - shutdownTimerService.waitForPlayingCompleted = - value), - ), - ), ), ], const SizedBox(height: 10), - Row( - children: [ - const SizedBox(width: 18), - const Text('倒计时结束:', style: titleStyle), - const Spacer(), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = false; - setState(() {}); - }, - text: " 暂停视频 ", - selectStatus: !shutdownTimerService.exitApp, - ), - const Spacer(), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = true; - setState(() {}); - }, - text: " 退出APP ", - selectStatus: shutdownTimerService.exitApp, - ), - const SizedBox(width: 25), - ], + Builder( + builder: (context) { + return Row( + children: [ + const SizedBox(width: 18), + const Text('倒计时结束:', style: titleStyle), + const Spacer(), + ActionRowLineItem( + onTap: () { + shutdownTimerService.exitApp = false; + (context as Element).markNeedsBuild(); + }, + text: " 暂停视频 ", + selectStatus: !shutdownTimerService.exitApp, + ), + const Spacer(), + ActionRowLineItem( + onTap: () { + shutdownTimerService.exitApp = true; + (context as Element).markNeedsBuild(); + }, + text: " 退出APP ", + selectStatus: shutdownTimerService.exitApp, + ), + const SizedBox(width: 25), + ], + ); + }, ), ], ),