diff --git a/lib/common/skeleton/skeleton.dart b/lib/common/skeleton/skeleton.dart index a2bcf8c75..d8b75fd81 100644 --- a/lib/common/skeleton/skeleton.dart +++ b/lib/common/skeleton/skeleton.dart @@ -1,188 +1,62 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; -class Skeleton extends StatelessWidget { +class Skeleton extends StatefulWidget { final Widget child; - const Skeleton({ - required this.child, - super.key, - }); + const Skeleton({super.key, required this.child}); @override - Widget build(BuildContext context) { - final color = Theme.of(context).colorScheme.surface.withAlpha(10); - final shimmerGradient = LinearGradient( - colors: [ - Colors.transparent, - color, - color, - Colors.transparent, - ], - stops: const [ - 0.1, - 0.3, - 0.5, - 0.7, - ], - begin: const Alignment(-1.0, -0.3), - end: const Alignment(1.0, 0.9), - tileMode: TileMode.clamp, - ); - return Shimmer( - linearGradient: shimmerGradient, - child: ShimmerLoading( - isLoading: true, - child: child, - ), - ); - } + State createState() => _SkeletonState(); } -class Shimmer extends StatefulWidget { - static ShimmerState? of(BuildContext context) { - return context.findAncestorStateOfType(); - } - - const Shimmer({ - super.key, - required this.linearGradient, - this.child, - }); - - final LinearGradient linearGradient; - final Widget? child; - - @override - ShimmerState createState() => ShimmerState(); -} - -class ShimmerState extends State with SingleTickerProviderStateMixin { - late AnimationController _shimmerController; +class _SkeletonState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late Color color; + final matrix = Matrix4.identity(); @override void initState() { super.initState(); - _shimmerController = AnimationController.unbounded(vsync: this) - ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)); + _controller = AnimationController.unbounded(vsync: this) + ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)) + ..addListener(_setState); } @override void dispose() { - _shimmerController.dispose(); + _controller.dispose(); super.dispose(); } - LinearGradient get gradient => LinearGradient( - colors: widget.linearGradient.colors, - stops: widget.linearGradient.stops, - begin: widget.linearGradient.begin, - end: widget.linearGradient.end, - transform: _SlidingGradientTransform( - slidePercent: _shimmerController.value, - ), - ); - - bool get isSized => - (context.findRenderObject() as RenderBox?)?.hasSize ?? false; - - Size get size => (context.findRenderObject() as RenderBox).size; - - Offset getDescendantOffset({ - required RenderBox descendant, - Offset offset = Offset.zero, - }) { - final shimmerBox = context.findRenderObject() as RenderBox; - return descendant.localToGlobal(offset, ancestor: shimmerBox); + void _setState() { + setState(() {}); } - Listenable get shimmerChanges => _shimmerController; - - @override - Widget build(BuildContext context) { - return widget.child ?? const SizedBox.shrink(); - } -} - -class _SlidingGradientTransform extends GradientTransform { - const _SlidingGradientTransform({ - required this.slidePercent, - }); - - final double slidePercent; - - @override - Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { - return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); - } -} - -class ShimmerLoading extends StatefulWidget { - const ShimmerLoading({ - super.key, - required this.isLoading, - required this.child, - }); - - final bool isLoading; - final Widget child; - - @override - State createState() => _ShimmerLoadingState(); -} - -class _ShimmerLoadingState extends State { - Listenable? _shimmerChanges; - @override void didChangeDependencies() { super.didChangeDependencies(); - if (_shimmerChanges != null) { - _shimmerChanges!.removeListener(_onShimmerChange); - } - _shimmerChanges = Shimmer.of(context)?.shimmerChanges; - if (_shimmerChanges != null) { - _shimmerChanges!.addListener(_onShimmerChange); - } - } - - @override - void dispose() { - _shimmerChanges?.removeListener(_onShimmerChange); - super.dispose(); - } - - void _onShimmerChange() { - if (widget.isLoading) { - setState(() {}); - } + color = ColorScheme.of(context).surface.withAlpha(10); } @override Widget build(BuildContext context) { - if (!widget.isLoading) { - return widget.child; - } - - final shimmer = Shimmer.of(context)!; - if (!shimmer.isSized) { - return const SizedBox.shrink(); - } - final shimmerSize = shimmer.size; - final gradient = shimmer.gradient; - final offsetWithinShimmer = shimmer.getDescendantOffset( - descendant: context.findRenderObject() as RenderBox, - ); - + final colors = [Colors.transparent, color, color, Colors.transparent]; return ShaderMask( blendMode: BlendMode.srcATop, - shaderCallback: (bounds) { - return gradient.createShader( - Rect.fromLTWH( - -offsetWithinShimmer.dx, - -offsetWithinShimmer.dy, - shimmerSize.width, - shimmerSize.height, - ), + shaderCallback: (Rect bounds) { + final width = bounds.width; + final height = bounds.height; + matrix[12] = width * _controller.value; + return ui.Gradient.linear( + Offset(0, 0.35 * height), + Offset(width, 0.95 * height), + colors, + const [0.1, 0.3, 0.5, 0.7], + TileMode.clamp, + matrix.storage, ); }, child: widget.child, diff --git a/lib/common/widgets/dialog/export_import.dart b/lib/common/widgets/dialog/export_import.dart index 4d01ff6c0..e77f1f178 100644 --- a/lib/common/widgets/dialog/export_import.dart +++ b/lib/common/widgets/dialog/export_import.dart @@ -3,7 +3,7 @@ import 'dart:convert' show utf8, jsonDecode; import 'dart:io' show File; import 'package:PiliPlus/common/style.dart'; -import 'package:PiliPlus/utils/extension/context_ext.dart'; +import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/storage_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:file_picker/file_picker.dart'; @@ -46,87 +46,91 @@ Future importFromClipBoard( bool showConfirmDialog = true, }) async { final data = await Clipboard.getData('text/plain'); - if (data?.text?.isNotEmpty != true) { - SmartDialog.showToast('剪贴板无数据'); - return; - } - if (!context.mounted) return; - final text = data!.text!; - late final T json; - late final String formatText; - try { - json = jsonDecode(text); - formatText = Utils.jsonEncoder.convert(json); - } catch (e) { - SmartDialog.showToast('解析json失败:$e'); - return; - } - bool? executeImport; - if (showConfirmDialog) { - final highlight = Highlight()..registerLanguage('json', langJson); - final result = highlight.highlight( - code: formatText, - language: 'json', - ); - late TextSpanRenderer renderer; - bool? isDarkMode; - executeImport = await showDialog( - context: context, - builder: (context) { - final isDark = context.isDarkMode; - if (isDark != isDarkMode) { - isDarkMode = isDark; - renderer = TextSpanRenderer( - const TextStyle(), - isDark ? githubDarkTheme : githubTheme, - ); - result.render(renderer); - } - return AlertDialog( - title: Text('是否导入如下$title?'), - content: SingleChildScrollView( - child: Text.rich(renderer.span!), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, + if (data?.text case final text? when (text.isNotEmpty)) { + if (!context.mounted) return; + final T json; + final String formatText; + try { + json = jsonDecode(text); + formatText = Utils.jsonEncoder.convert(json); + } catch (e) { + SmartDialog.showToast('解析json失败:$e'); + return; + } + bool? executeImport; + if (showConfirmDialog) { + final highlight = Highlight()..registerLanguage('json', langJson); + final result = highlight.highlight( + code: formatText, + language: 'json', + ); + late TextSpanRenderer renderer; + bool? isDarkMode; + executeImport = await showDialog( + context: context, + builder: (context) { + final theme = Theme.of(context); + final isDark = theme.brightness.isDark; + if (isDark != isDarkMode) { + isDarkMode = isDark; + renderer = TextSpanRenderer( + const TextStyle(), + isDark ? githubDarkTheme : githubTheme, + ); + result.render(renderer); + } + return AlertDialog( + title: Text('是否导入如下$title?'), + content: SingleChildScrollView( + child: Text.rich(renderer.span!), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: theme.colorScheme.outline, + ), ), ), - ), - TextButton( - onPressed: () => Get.back(result: true), - child: const Text('确定'), - ), - ], - ); - }, - ); - } else { - executeImport = true; - } - if (executeImport ?? false) { - try { - await onImport(json); - SmartDialog.showToast('导入成功'); - } catch (e) { - SmartDialog.showToast('导入失败:$e'); + TextButton( + onPressed: () => Get.back(result: true), + child: const Text('确定'), + ), + ], + ); + }, + ); + } else { + executeImport = true; } + if (executeImport ?? false) { + try { + await onImport(json); + SmartDialog.showToast('导入成功'); + } catch (e) { + SmartDialog.showToast('导入失败:$e'); + } + } + } else { + SmartDialog.showToast('剪贴板无数据'); + return; } } Future importFromLocalFile({ required FutureOr Function(T json) onImport, }) async { - final result = await FilePicker.pickFiles(); + final result = await FilePicker.pickFiles( + type: .custom, + allowedExtensions: const ['json', 'txt'], + ); if (result != null) { final path = result.files.first.path; if (path != null) { final data = await File(path).readAsString(); - late final T json; + final T json; try { json = jsonDecode(data); } catch (e) { @@ -172,7 +176,6 @@ void importFromInput( json = jsonDecode(value!) as T; return null; } catch (e) { - if (e is FormatException) {} return '解析json失败:$e'; } }, diff --git a/lib/common/widgets/select_mask.dart b/lib/common/widgets/select_mask.dart index 5c964bc87..b6ce4e926 100644 --- a/lib/common/widgets/select_mask.dart +++ b/lib/common/widgets/select_mask.dart @@ -2,7 +2,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:flutter/material.dart'; Widget selectMask( - ThemeData theme, + ColorScheme colorScheme, bool checked, { BorderRadiusGeometry borderRadius = Style.mdRadius, }) { @@ -23,12 +23,12 @@ Widget selectMask( width: 34, height: 34, decoration: BoxDecoration( - color: theme.colorScheme.surface.withValues(alpha: 0.8), + color: colorScheme.surface.withValues(alpha: 0.8), shape: BoxShape.circle, ), child: Icon( Icons.done_all_outlined, - color: theme.colorScheme.primary, + color: colorScheme.primary, semanticLabel: '取消选择', ), ), diff --git a/lib/http/video.dart b/lib/http/video.dart index 2766f3c06..ba8d4aa9d 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -210,6 +210,8 @@ abstract final class VideoHttp { String? language, bool voiceBalance = false, }) async { + final dmImgStr = Utils.base64EncodeRandomString(16, 64); + final dmCoverImgStr = Utils.base64EncodeRandomString(32, 128); final params = await WbiSign.makSign({ 'avid': ?avid, 'bvid': ?bvid, @@ -227,6 +229,10 @@ abstract final class VideoHttp { 'web_location': 1315873, // 免登录查看1080p if (tryLook) 'try_look': 1, + 'dm_img_list': '[]', + 'dm_img_str': dmImgStr, + 'dm_cover_img_str': dmCoverImgStr, + 'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}', 'cur_language': ?language, }); diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart index 5a1c8da3c..aecececd9 100644 --- a/lib/pages/article/controller.dart +++ b/lib/pages/article/controller.dart @@ -39,7 +39,7 @@ class ArticleController extends CommonDynController { final RxBool isLoaded = false.obs; DynamicItemModel? opusData; // 标题信息从summary获取, 动态没有favorite ArticleViewData? articleData; - final Rx stats = Rx(null); + final stats = Rxn(); List? get opus => opusData?.modules.moduleContent ?? articleData?.opus?.content; diff --git a/lib/pages/article_list/controller.dart b/lib/pages/article_list/controller.dart index 660d8d515..3c3dbb673 100644 --- a/lib/pages/article_list/controller.dart +++ b/lib/pages/article_list/controller.dart @@ -17,7 +17,7 @@ class ArticleListController queryData(); } - Rx list = Rx(null); + final list = Rxn(); Owner? author; @override diff --git a/lib/pages/download/detail/view.dart b/lib/pages/download/detail/view.dart index be1312dda..3c91b1f9c 100644 --- a/lib/pages/download/detail/view.dart +++ b/lib/pages/download/detail/view.dart @@ -103,16 +103,16 @@ class _DownloadDetailPageState extends State visualDensity: VisualDensity.compact, ), onPressed: () async { - final allChecked = this.allChecked.toSet(); + final futures = allChecked + .map( + (e) => _downloadService.downloadDanmaku( + entry: e, + isUpdate: true, + ), + ) + .toList(); handleSelect(); - final res = await Future.wait( - allChecked.map( - (e) => _downloadService.downloadDanmaku( - entry: e, - isUpdate: true, - ), - ), - ); + final res = await Future.wait(futures); if (res.every((e) => e)) { SmartDialog.showToast('更新成功'); } else { @@ -201,22 +201,20 @@ class _DownloadDetailPageState extends State title: const Text('确定删除选中视频?'), onConfirm: () async { SmartDialog.showLoading(); - final watchProgress = GStorage.watchProgress; - final allChecked = this.allChecked.toSet(); + final allChecked = this.allChecked.toList(); final isDeleteAll = allChecked.length == _downloadItems.length; - if (isDeleteAll) { - await _closeSub(); - } - for (final entry in allChecked) { - await watchProgress.deleteAll( + await Future.wait([ + if (isDeleteAll) _closeSub(), + GStorage.watchProgress.deleteAll( allChecked.map((e) => e.cid.toString()), - ); - await _downloadService.deleteDownload( - entry: entry, - removeList: true, - refresh: false, - ); - } + ), + for (final entry in allChecked) + _downloadService.deleteDownload( + entry: entry, + removeList: true, + refresh: false, + ), + ]); _downloadService.flagNotifier.refresh(); if (isDeleteAll) { SmartDialog.dismiss(); diff --git a/lib/pages/download/detail/widgets/item.dart b/lib/pages/download/detail/widgets/item.dart index e50e453d0..5a088b272 100644 --- a/lib/pages/download/detail/widgets/item.dart +++ b/lib/pages/download/detail/widgets/item.dart @@ -275,7 +275,10 @@ class DetailItem extends StatelessWidget { type: PBadgeType.gray, ), Positioned.fill( - child: selectMask(theme, checked ?? entry.checked), + child: selectMask( + theme.colorScheme, + checked ?? entry.checked, + ), ), ], ), diff --git a/lib/pages/download/search/view.dart b/lib/pages/download/search/view.dart index 9451b362c..dd4d6dfb3 100644 --- a/lib/pages/download/search/view.dart +++ b/lib/pages/download/search/view.dart @@ -57,16 +57,16 @@ class _DownloadSearchPageState TextButton( style: TextButton.styleFrom(visualDensity: VisualDensity.compact), onPressed: () async { - final allChecked = controller.allChecked.toSet(); + final future = controller.allChecked + .map( + (e) => _downloadService.downloadDanmaku( + entry: e, + isUpdate: true, + ), + ) + .toList(); controller.handleSelect(); - final res = await Future.wait( - allChecked.map( - (e) => _downloadService.downloadDanmaku( - entry: e, - isUpdate: true, - ), - ), - ); + final res = await Future.wait(future); if (res.every((e) => e)) { SmartDialog.showToast('更新成功'); } else { diff --git a/lib/pages/download/view.dart b/lib/pages/download/view.dart index b743e2040..92b6f8d53 100644 --- a/lib/pages/download/view.dart +++ b/lib/pages/download/view.dart @@ -10,13 +10,13 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; -import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; import 'package:PiliPlus/models_new/download/download_info.dart'; import 'package:PiliPlus/pages/download/controller.dart'; import 'package:PiliPlus/pages/download/detail/view.dart'; import 'package:PiliPlus/pages/download/detail/widgets/item.dart'; import 'package:PiliPlus/pages/download/search/view.dart'; import 'package:PiliPlus/services/download/download_service.dart'; +import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart' show IterableExt; import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -67,20 +67,16 @@ class _DownloadPageState extends State { visualDensity: VisualDensity.compact, ), onPressed: () async { - final allChecked = _controller.allChecked.toSet(); + final future = [ + for (final page in _controller.allChecked) + for (final e in page.entries) + _downloadService.downloadDanmaku( + entry: e, + isUpdate: true, + ), + ]; _controller.handleSelect(); - final list = []; - for (final page in allChecked) { - list.addAll(page.entries); - } - final res = await Future.wait( - list.map( - (e) => _downloadService.downloadDanmaku( - entry: e, - isUpdate: true, - ), - ), - ); + final res = await Future.wait(future); if (res.every((e) => e)) { SmartDialog.showToast('更新成功'); } else { @@ -355,7 +351,7 @@ class _DownloadPageState extends State { top: 6.0, ), Positioned.fill( - child: selectMask(theme, pageInfo.checked), + child: selectMask(theme.colorScheme, pageInfo.checked), ), ], ), @@ -381,17 +377,14 @@ class _DownloadPageState extends State { crossAxisAlignment: .end, mainAxisAlignment: .spaceBetween, children: [ - if (first.ownerName case final ownerName?) - Text( - ownerName, - style: TextStyle( - fontSize: 12, - height: 1.6, - color: theme.colorScheme.outline, - ), - ) - else - const Spacer(), + Text( + '${CacheManager.formatSize(pageInfo.entries.fold(0, (p, n) => p + n.totalBytes))} ${first.ownerName ?? ""}', + style: TextStyle( + fontSize: 12, + height: 1.6, + color: theme.colorScheme.outline, + ), + ), pageInfo.entries.first.moreBtn(theme), ], ), diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index 8dfe8da83..41657e3f3 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -104,8 +104,8 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { late final Rx?> _topic; late final Rx _replyOption; late final TextEditingController _titleEditCtr; - late final Rx _publishTime = Rx(null); - final Rx _reserveCard = Rx(null); + late final _publishTime = Rxn(); + final _reserveCard = Rxn(); @override void initState() { diff --git a/lib/pages/dynamics_topic/controller.dart b/lib/pages/dynamics_topic/controller.dart index 1c7623dc1..17e8b9641 100644 --- a/lib/pages/dynamics_topic/controller.dart +++ b/lib/pages/dynamics_topic/controller.dart @@ -18,13 +18,13 @@ class DynTopicController int sortBy = 0; String offset = ''; - Rx topicSortByConf = Rx(null); + final topicSortByConf = Rxn(); double? appbarOffset; // top - Rx isFav = Rx(null); - Rx isLike = Rx(null); + final isFav = false.obs; + final isLike = false.obs; Rx> topState = LoadingState.loading().obs; @@ -42,8 +42,8 @@ class DynTopicController if (topState.value case Success(:final response)) { final topicItem = response!.topicItem!; topicName = topicItem.name; - isFav.value = topicItem.isFav; - isLike.value = topicItem.isLike; + isFav.value = topicItem.isFav ?? false; + isLike.value = topicItem.isLike ?? false; } } @@ -96,7 +96,7 @@ class DynTopicController SmartDialog.showToast('账号未登录'); return; } - bool isFav = this.isFav.value ?? false; + final isFav = this.isFav.value; final res = isFav ? await FavHttp.delFavTopic(topicId) : await FavHttp.addFavTopic(topicId); @@ -117,7 +117,7 @@ class DynTopicController SmartDialog.showToast('账号未登录'); return; } - bool isLike = this.isLike.value ?? false; + final isLike = this.isLike.value; final res = await FavHttp.likeTopic(topicId, isLike); if (res.isSuccess) { if (isLike) { diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index 6c76d3e40..d7c1114ae 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -45,7 +45,7 @@ class _DynTopicPageState extends State with DynMixin { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, @@ -74,7 +74,7 @@ class _DynTopicPageState extends State with DynMixin { slivers: [ Obx( () => _buildAppBar( - theme, + colorScheme, padding, _controller.topState.value, ), @@ -83,7 +83,7 @@ class _DynTopicPageState extends State with DynMixin { final allSortBy = _controller.topicSortByConf.value?.allSortBy; if (allSortBy != null && allSortBy.isNotEmpty) { return SliverPinnedHeader( - backgroundColor: theme.colorScheme.surface, + backgroundColor: colorScheme.surface, child: Padding( padding: EdgeInsets.only( left: 12 + padding.left, @@ -93,8 +93,8 @@ class _DynTopicPageState extends State with DynMixin { child: Builder( builder: (context) { return ToggleButtons( - fillColor: theme.colorScheme.secondaryContainer, - selectedColor: theme.colorScheme.onSecondaryContainer, + fillColor: colorScheme.secondaryContainer, + selectedColor: colorScheme.onSecondaryContainer, constraints: const BoxConstraints( minWidth: 54, minHeight: 24, @@ -148,7 +148,7 @@ class _DynTopicPageState extends State with DynMixin { } Widget _buildAppBar( - ThemeData theme, + ColorScheme colorScheme, EdgeInsets padding, LoadingState topState, ) { @@ -205,7 +205,7 @@ class _DynTopicPageState extends State with DynMixin { ), Text( ' 发起', - style: TextStyle(color: theme.colorScheme.outline), + style: TextStyle(color: colorScheme.outline), ), ], ), @@ -221,7 +221,7 @@ class _DynTopicPageState extends State with DynMixin { const SizedBox(height: 6), SelectableText( response.topicItem!.description!, - style: TextStyle(color: theme.colorScheme.onSurfaceVariant), + style: TextStyle(color: colorScheme.onSurfaceVariant), ), const SizedBox(height: 10), Row( @@ -230,7 +230,7 @@ class _DynTopicPageState extends State with DynMixin { '${NumUtils.numFormat(response.topicItem!.view)}浏览 · ${NumUtils.numFormat(response.topicItem!.discuss)}讨论', style: TextStyle( fontSize: 13, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), const Spacer(), @@ -238,13 +238,11 @@ class _DynTopicPageState extends State with DynMixin { style: OutlinedButton.styleFrom( side: BorderSide( width: 1, - color: theme.colorScheme.outline.withValues( - alpha: 0.2, - ), + color: colorScheme.outline.withValues(alpha: 0.2), ), - foregroundColor: _controller.isLike.value == true + foregroundColor: _controller.isLike.value ? null - : theme.colorScheme.onSurfaceVariant, + : colorScheme.onSurfaceVariant, padding: const EdgeInsets.symmetric(horizontal: 10), visualDensity: const VisualDensity( horizontal: -4, @@ -253,7 +251,7 @@ class _DynTopicPageState extends State with DynMixin { tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: _controller.onLike, - icon: _controller.isLike.value == true + icon: _controller.isLike.value ? const Icon(FontAwesomeIcons.solidThumbsUp, size: 13) : const Icon(FontAwesomeIcons.thumbsUp, size: 13), label: Text( @@ -267,22 +265,17 @@ class _DynTopicPageState extends State with DynMixin { style: OutlinedButton.styleFrom( side: BorderSide( width: 1, - color: theme.colorScheme.outline.withValues( - alpha: 0.2, - ), + color: colorScheme.outline.withValues(alpha: 0.2), ), - foregroundColor: _controller.isFav.value == true + foregroundColor: _controller.isFav.value ? null - : theme.colorScheme.onSurfaceVariant, + : colorScheme.onSurfaceVariant, padding: const EdgeInsets.symmetric(horizontal: 10), - visualDensity: const VisualDensity( - horizontal: -4, - vertical: -4, - ), + visualDensity: const .new(horizontal: -4, vertical: -4), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: _controller.onFav, - icon: _controller.isFav.value == true + icon: _controller.isFav.value ? const Icon(FontAwesomeIcons.solidStar, size: 13) : const Icon(FontAwesomeIcons.star, size: 13), label: Text( @@ -311,7 +304,7 @@ class _DynTopicPageState extends State with DynMixin { PopupMenuItem( onTap: _controller.onFav, child: Text( - '${_controller.isFav.value == true ? '取消' : ''}收藏', + '${_controller.isFav.value ? '取消' : ''}收藏', ), ), PopupMenuItem( @@ -322,7 +315,7 @@ class _DynTopicPageState extends State with DynMixin { return; } PageUtils.inAppWebview( - 'https://www.bilibili.com/h5/topic-active/topic-report?topic_id=${_controller.topicId}&topic_name=${_controller.topicName}&${ThemeUtils.themeUrl(theme.isDark)}', + 'https://www.bilibili.com/h5/topic-active/topic-report?topic_id=${_controller.topicId}&topic_name=${_controller.topicName}&${ThemeUtils.themeUrl(colorScheme.isDark)}', ); }, ), diff --git a/lib/pages/fav/note/widget/item.dart b/lib/pages/fav/note/widget/item.dart index 61396624e..baab037d7 100644 --- a/lib/pages/fav/note/widget/item.dart +++ b/lib/pages/fav/note/widget/item.dart @@ -29,7 +29,7 @@ class FavNoteItem extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return Material( type: MaterialType.transparency, child: InkWell( @@ -71,7 +71,7 @@ class FavNoteItem extends StatelessWidget { ), Positioned.fill( child: selectMask( - theme, + colorScheme, item.checked, ), ), @@ -100,7 +100,7 @@ class FavNoteItem extends StatelessWidget { style: TextStyle( fontSize: 14, height: 1, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), const Spacer(), @@ -110,7 +110,7 @@ class FavNoteItem extends StatelessWidget { style: TextStyle( fontSize: 13, height: 1, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), ], diff --git a/lib/pages/fav/pgc/widget/item.dart b/lib/pages/fav/pgc/widget/item.dart index 3e4bd3247..2667d0bc7 100644 --- a/lib/pages/fav/pgc/widget/item.dart +++ b/lib/pages/fav/pgc/widget/item.dart @@ -34,7 +34,7 @@ class FavPgcItem extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return Material( type: MaterialType.transparency, child: Stack( @@ -86,7 +86,7 @@ class FavPgcItem extends StatelessWidget { ), Positioned.fill( child: selectMask( - theme, + colorScheme, item.checked, borderRadius: const BorderRadius.all( Radius.circular(4), @@ -111,7 +111,7 @@ class FavPgcItem extends StatelessWidget { '${item.newEp!.indexShow}${item.isFinish == 0 && item.renewalTime?.isNotEmpty == true ? ',${item.renewalTime}' : ''}', style: TextStyle( fontSize: 13, - color: theme.colorScheme.onSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ], @@ -123,7 +123,7 @@ class FavPgcItem extends StatelessWidget { item.progress!, style: TextStyle( fontSize: 13, - color: theme.colorScheme.onSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ], @@ -141,7 +141,7 @@ class FavPgcItem extends StatelessWidget { iconSize: 18, onPressed: onUpdateStatus, icon: const Icon(Icons.more_vert), - iconColor: theme.colorScheme.outline, + iconColor: colorScheme.outline, ), ), ], diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index c22f21756..7485e9c6f 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -80,11 +80,11 @@ class FavDetailController late int mediaId; late String heroTag; final Rx folderInfo = FavFolderInfo().obs; - final Rx _isOwner = Rx(null); + final RxBool _isOwner = false.obs; final Rx order = FavOrderType.mtime.obs; @override - bool get isOwner => _isOwner.value ?? false; + bool get isOwner => _isOwner.value; late final account = Accounts.main; diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 1cbe34444..b1ba30eef 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -39,7 +39,7 @@ class FavVideoCardH extends StatelessWidget { Widget build(BuildContext context) { final isOwner = !isSort && ctr!.isOwner; late final enableMultiSelect = ctr?.enableMultiSelect.value ?? false; - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); final onLongPress = isSort || enableMultiSelect ? null @@ -135,7 +135,7 @@ class FavVideoCardH extends StatelessWidget { if (!isSort) Positioned.fill( child: selectMask( - theme, + colorScheme, item.checked, ), ), @@ -145,7 +145,7 @@ class FavVideoCardH extends StatelessWidget { ), ), const SizedBox(width: 10), - content(context, theme, isOwner), + content(context, colorScheme, isOwner), ], ), ), @@ -153,7 +153,7 @@ class FavVideoCardH extends StatelessWidget { ); } - Widget content(BuildContext context, ThemeData theme, isOwner) { + Widget content(BuildContext context, ColorScheme colorScheme, bool isOwner) { return Expanded( child: Stack( clipBehavior: Clip.none, @@ -178,7 +178,7 @@ class FavVideoCardH extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), const Spacer(), @@ -189,7 +189,7 @@ class FavVideoCardH extends StatelessWidget { style: TextStyle( height: 1, fontSize: 12, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), if (item.type != 24) @@ -215,7 +215,7 @@ class FavVideoCardH extends StatelessWidget { child: iconButton( icon: const Icon(Icons.clear), tooltip: '取消收藏', - iconColor: theme.colorScheme.outline, + iconColor: colorScheme.outline, onPressed: () => showDialog( context: context, builder: (context) => AlertDialog( @@ -226,7 +226,7 @@ class FavVideoCardH extends StatelessWidget { onPressed: Get.back, child: Text( '取消', - style: TextStyle(color: theme.colorScheme.outline), + style: TextStyle(color: colorScheme.outline), ), ), TextButton( diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 6b1316602..752afc3d5 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -177,7 +177,10 @@ class HistoryItem extends StatelessWidget { ), ), Positioned.fill( - child: selectMask(theme, item.checked), + child: selectMask( + theme.colorScheme, + item.checked, + ), ), ], ); diff --git a/lib/pages/later/widgets/video_card_h_later.dart b/lib/pages/later/widgets/video_card_h_later.dart index 3edc47229..2127ea589 100644 --- a/lib/pages/later/widgets/video_card_h_later.dart +++ b/lib/pages/later/widgets/video_card_h_later.dart @@ -158,7 +158,7 @@ class VideoCardHLater extends StatelessWidget { ), Positioned.fill( child: selectMask( - theme, + theme.colorScheme, videoItem.checked, ), ), diff --git a/lib/pages/live_follow/controller.dart b/lib/pages/live_follow/controller.dart index 7fe52762c..98a663613 100644 --- a/lib/pages/live_follow/controller.dart +++ b/lib/pages/live_follow/controller.dart @@ -13,7 +13,7 @@ class LiveFollowController queryData(); } - Rx count = Rx(null); + final count = RxnInt(); @override void checkIsEnd(int length) { diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index e9d8a986b..4a3252c44 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -52,14 +52,14 @@ class LiveRoomController extends GetxController { int roomId = Get.arguments; int? ruid; DanmakuController? danmakuController; - PlPlayerController plPlayerController = PlPlayerController.getInstance( + final plPlayerController = PlPlayerController.getInstance( isLive: true, ); - RxBool isLoaded = false.obs; - Rx roomInfoH5 = Rx(null); + final isLoaded = false.obs; + final roomInfoH5 = Rxn(); - Rx liveTime = Rx(null); + final liveTime = Rxn(); Timer? liveTimeTimer; void startLiveTimer() { @@ -102,11 +102,11 @@ class LiveRoomController extends GetxController { LiveDmInfoData? dmInfo; List? savedDanmaku; int builtLength = 0; - RxList messages = [].obs; + final messages = [].obs; bool get shouldRefresh => builtLength != messages.length; - late final Rx fsSC = Rx(null); + late final fsSC = Rxn(); late final RxList superChatMsg = [].obs; - RxBool disableAutoScroll = false.obs; + final disableAutoScroll = false.obs; bool autoScroll = true; LiveMessageStream? _msgStream; late final ScrollController scrollController; @@ -114,7 +114,7 @@ class LiveRoomController extends GetxController { PageController? pageController; int? currentQn = PlatformUtils.isMobile ? null : Pref.liveQuality; - RxString currentQnDesc = ''.obs; + final currentQnDesc = ''.obs; final RxBool isPortrait = false.obs; late List<({int code, String desc})> acceptQnList = []; diff --git a/lib/pages/my_reply/view.dart b/lib/pages/my_reply/view.dart index a10553dc7..1136d8517 100644 --- a/lib/pages/my_reply/view.dart +++ b/lib/pages/my_reply/view.dart @@ -15,9 +15,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/waterfall.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; -import 'package:get/get_core/src/get_main.dart'; -import 'package:get/get_navigation/src/extension_navigation.dart'; -import 'package:get/get_rx/get_rx.dart'; +import 'package:get/get.dart'; import 'package:waterfall_flow/waterfall_flow.dart'; class MyReply extends StatefulWidget { diff --git a/lib/pages/pgc_review/child/controller.dart b/lib/pages/pgc_review/child/controller.dart index 92b5daa29..91eec433b 100644 --- a/lib/pages/pgc_review/child/controller.dart +++ b/lib/pages/pgc_review/child/controller.dart @@ -14,9 +14,9 @@ class PgcReviewController final PgcReviewType type; final dynamic mediaId; - Rx count = Rx(null); + final count = RxnInt(); String? next; - Rx sortType = PgcReviewSortType.def.obs; + final sortType = PgcReviewSortType.def.obs; @override void onInit() { diff --git a/lib/pages/popular_series/controller.dart b/lib/pages/popular_series/controller.dart index cf58a5451..1657fb66a 100644 --- a/lib/pages/popular_series/controller.dart +++ b/lib/pages/popular_series/controller.dart @@ -14,7 +14,7 @@ class PopularSeriesController with ReloadMixin { late int number; - final Rx config = Rx(null); + final config = Rxn(); String? reminder; List? seriesList; diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 0e9b23fa2..4c6f712b7 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -807,8 +807,8 @@ class VideoDetailController extends GetxController bool isQuerying = false; - final Rx?> languages = Rx?>(null); - final Rx currLang = Rx(null); + final languages = Rxn>(); + final currLang = Rxn(); void setLanguage(String language) { if (currLang.value == language) return; if (!isLoginVideo) { diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 5b853abda..52f33a528 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -467,14 +467,21 @@ class _PostPanelState extends State final player = plPlayerController.videoPlayerController; if (player != null) { final start = (item.segment.first * 1000).round(); + Future seekTo() => player.seek( + Duration(milliseconds: (item.segment.second * 1000).round()), + ); + if (start <= 0) { + seekTo(); + if (!player.state.playing) { + await player.play(); + } + return; + } final seek = max(0, start - 2000); await player.seek(Duration(milliseconds: seek)); if (!player.state.playing) { await player.play(); } - Future seekTo() => player.seek( - Duration(milliseconds: (item.segment.second * 1000).round()), - ); if (start > seek) { final posSub = player.stream.position.listen( null, diff --git a/lib/pages/video/reply_reply/controller.dart b/lib/pages/video/reply_reply/controller.dart index 29ebcded3..026c78c32 100644 --- a/lib/pages/video/reply_reply/controller.dart +++ b/lib/pages/video/reply_reply/controller.dart @@ -33,7 +33,7 @@ class VideoReplyReplyController extends ReplyController int replyType; bool hasRoot = false; - final Rx firstFloor = Rx(null); + final firstFloor = Rxn(); final index = RxnInt(); diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 8a645007a..4e74d1a2f 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -668,7 +668,10 @@ class HeaderControlState extends State onTap: () async { Get.back(); try { - final result = await FilePicker.pickFiles(); + final result = await FilePicker.pickFiles( + type: .custom, + allowedExtensions: const ['json', 'vtt', 'srt'], + ); if (result != null) { final file = result.files.single; final path = file.path; diff --git a/lib/utils/color_utils.dart b/lib/utils/color_utils.dart index 58c85137f..e1faca74f 100644 --- a/lib/utils/color_utils.dart +++ b/lib/utils/color_utils.dart @@ -2,11 +2,12 @@ import 'package:flutter/rendering.dart' show Color; abstract final class ColourUtils { static Color parseColor(String color) => - Color(int.parse('FF${color.substring(1)}', radix: 16)); + Color(0xFF000000 | int.parse(color.substring(1), radix: 16)); - static Color parseMedalColor(String color) => Color( - int.parse('${color.substring(7)}${color.substring(1, 7)}', radix: 16), - ); + static Color parseMedalColor(String color) { + final rgba = int.parse(color.substring(1), radix: 16); + return Color.fromARGB(rgba, rgba >> 24, rgba >> 16, rgba >> 8); + } static Color index2Color(int index, Color color) => switch (index) { 0 => const Color(0xFFfdad13), diff --git a/lib/utils/max_screen_size.dart b/lib/utils/max_screen_size.dart index cc51bfed6..9fd4cdf5b 100644 --- a/lib/utils/max_screen_size.dart +++ b/lib/utils/max_screen_size.dart @@ -14,17 +14,17 @@ abstract final class MaxScreenSize { static Future _initFoldable() async { final isFoldable = await Utils.channel.invokeMethod('isFoldable'); if (isFoldable == true) { - const MethodChannel('ScreenChannel').setMethodCallHandler((call) async { + const MethodChannel('ScreenChannel').setMethodCallHandler((call) { if (call.method == 'onConfigChanged') { _handleRes(call.arguments); } + return Future.syncValue(null); }); } } - static Future _initScreenSize() async { - final res = await Utils.channel.invokeMethod('maxScreenSize'); - _handleRes(res); + static Future _initScreenSize() { + return Utils.channel.invokeMethod('maxScreenSize').then(_handleRes); } static void _handleRes(dynamic res) {