diff --git a/lib/build_config.dart b/lib/build_config.dart index f77eff987..1fecebada 100644 --- a/lib/build_config.dart +++ b/lib/build_config.dart @@ -1,4 +1,4 @@ -class BuildConfig { +abstract final class BuildConfig { static const int versionCode = int.fromEnvironment( 'pili.code', defaultValue: 1, diff --git a/lib/common/widgets/back_detector.dart b/lib/common/widgets/back_detector.dart new file mode 100644 index 000000000..2d5486fae --- /dev/null +++ b/lib/common/widgets/back_detector.dart @@ -0,0 +1,42 @@ +import 'package:flutter/gestures.dart' show kBackMouseButton; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show KeyDownEvent; + +class BackDetector extends StatelessWidget { + const BackDetector({ + super.key, + required this.onBack, + required this.child, + }); + + final Widget child; + + final VoidCallback onBack; + + @override + Widget build(BuildContext context) { + return Focus( + canRequestFocus: false, + onKeyEvent: _onKeyEvent, + child: Listener( + behavior: .translucent, + onPointerDown: _onPointerDown, + child: child, + ), + ); + } + + KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) { + if (event.logicalKey == .escape && event is KeyDownEvent) { + onBack(); + return .handled; + } + return .ignored; + } + + void _onPointerDown(PointerDownEvent event) { + if (event.buttons == kBackMouseButton) { + onBack(); + } + } +} diff --git a/lib/common/widgets/custom_tooltip.dart b/lib/common/widgets/custom_tooltip.dart index eea160d69..7e95600b1 100644 --- a/lib/common/widgets/custom_tooltip.dart +++ b/lib/common/widgets/custom_tooltip.dart @@ -34,6 +34,9 @@ class _CustomTooltipState extends State { final OverlayPortalController _overlayController = OverlayPortalController(); LongPressGestureRecognizer? _longPressRecognizer; + LongPressGestureRecognizer get longPressRecognizer => + _longPressRecognizer ??= LongPressGestureRecognizer() + ..onLongPress = _scheduleShowTooltip; void _scheduleShowTooltip() { _overlayController.show(); @@ -45,9 +48,7 @@ class _CustomTooltipState extends State { void _handlePointerDown(PointerDownEvent event) { assert(mounted); - (_longPressRecognizer ??= LongPressGestureRecognizer( - debugOwner: this, - )..onLongPress = _scheduleShowTooltip).addPointer(event); + longPressRecognizer.addPointer(event); } Widget _buildCustomTooltipOverlay(BuildContext context) { @@ -80,7 +81,7 @@ class _CustomTooltipState extends State { @override void dispose() { _longPressRecognizer - ?..onLongPressCancel = null + ?..onLongPress = null ..dispose(); _longPressRecognizer = null; super.dispose(); diff --git a/lib/common/widgets/flutter/pop_scope.dart b/lib/common/widgets/flutter/pop_scope.dart new file mode 100644 index 000000000..85401dc0f --- /dev/null +++ b/lib/common/widgets/flutter/pop_scope.dart @@ -0,0 +1,44 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +abstract class PopScopeState extends State + implements PopEntry { + ModalRoute? _route; + + @override + void onPopInvoked(bool didPop) {} + + @override + late final ValueNotifier canPopNotifier; + + void initCanPopNotifier() { + canPopNotifier = ValueNotifier(false); + } + + @override + void initState() { + super.initState(); + initCanPopNotifier(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final ModalRoute? nextRoute = ModalRoute.of(context); + if (nextRoute != _route) { + _route?.unregisterPopEntry(this); + _route = nextRoute; + _route?.registerPopEntry(this); + } + } + + @override + void dispose() { + _route?.unregisterPopEntry(this); + canPopNotifier.dispose(); + super.dispose(); + } +} diff --git a/lib/common/widgets/mouse_back.dart b/lib/common/widgets/mouse_back.dart deleted file mode 100644 index 9db8621e8..000000000 --- a/lib/common/widgets/mouse_back.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - -class MouseBackDetector extends StatelessWidget { - const MouseBackDetector({ - super.key, - required this.onTapDown, - required this.child, - }); - - final Widget child; - - final VoidCallback onTapDown; - - @override - Widget build(BuildContext context) { - return Listener( - onPointerDown: (event) { - if (event.buttons == kBackMouseButton) { - onTapDown(); - } - }, - behavior: HitTestBehavior.translucent, - child: child, - ); - } -} diff --git a/lib/common/widgets/scroll_behavior.dart b/lib/common/widgets/scroll_behavior.dart new file mode 100644 index 000000000..10520d10a --- /dev/null +++ b/lib/common/widgets/scroll_behavior.dart @@ -0,0 +1,25 @@ +import 'package:flutter/gestures.dart' show PointerDeviceKind; +import 'package:flutter/material.dart'; + +class CustomScrollBehavior extends MaterialScrollBehavior { + const CustomScrollBehavior(this.dragDevices); + + @override + Widget buildScrollbar( + BuildContext context, + Widget child, + ScrollableDetails details, + ) => child; + + @override + final Set dragDevices; +} + +const Set desktopDragDevices = { + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.invertedStylus, + PointerDeviceKind.trackpad, + PointerDeviceKind.unknown, + PointerDeviceKind.mouse, +}; diff --git a/lib/main.dart b/lib/main.dart index 123cc78bf..87d385e53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,9 +2,10 @@ import 'dart:io'; import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/back_detector.dart'; import 'package:PiliPlus/common/widgets/custom_toast.dart'; -import 'package:PiliPlus/common/widgets/mouse_back.dart'; import 'package:PiliPlus/common/widgets/scale_app.dart'; +import 'package:PiliPlus/common/widgets/scroll_behavior.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/models/common/theme/theme_color_type.dart'; import 'package:PiliPlus/router/app_pages.dart'; @@ -30,7 +31,6 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:catcher_2/catcher_2.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart' show PointerDeviceKind; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; @@ -45,20 +45,7 @@ import 'package:window_manager/window_manager.dart' hide calcWindowPosition; WebViewEnvironment? webViewEnvironment; -void main() async { - ScaledWidgetsFlutterBinding.ensureInitialized(); - MediaKit.ensureInitialized(); - tmpDirPath = (await getTemporaryDirectory()).path; - appSupportDirPath = (await getApplicationSupportDirectory()).path; - try { - await GStorage.init(); - } catch (e) { - await Utils.copyText(e.toString()); - if (kDebugMode) debugPrint('GStorage init error: $e'); - exit(0); - } - ScaledWidgetsFlutterBinding.instance.setScaleFactor(Pref.uiScale); - +Future _initDownPath() async { if (PlatformUtils.isDesktop) { final customDownPath = Pref.downloadPath; if (customDownPath != null && customDownPath.isNotEmpty) { @@ -86,6 +73,29 @@ void main() async { } else { downloadPath = defDownloadPath; } +} + +Future _initTmpPath() async { + tmpDirPath = (await getTemporaryDirectory()).path; +} + +Future _initAppPath() async { + appSupportDirPath = (await getApplicationSupportDirectory()).path; +} + +void main() async { + ScaledWidgetsFlutterBinding.ensureInitialized(); + MediaKit.ensureInitialized(); + await _initAppPath(); + try { + await GStorage.init(); + } catch (e) { + await Utils.copyText(e.toString()); + if (kDebugMode) debugPrint('GStorage init error: $e'); + exit(0); + } + ScaledWidgetsFlutterBinding.instance.setScaleFactor(Pref.uiScale); + await Future.wait([_initDownPath(), _initTmpPath()]); Get ..lazyPut(AccountService.new) ..lazyPut(DownloadService.new); @@ -106,9 +116,7 @@ void main() async { ), setupServiceLocator(), ]); - } - - if (Platform.isWindows) { + } else if (Platform.isWindows) { if (await WebViewEnvironment.getAvailableVersion() != null) { webViewEnvironment = await WebViewEnvironment.create( settings: WebViewEnvironmentSettings( @@ -279,66 +287,49 @@ class MyApp extends StatelessWidget { builder: FlutterSmartDialog.init( toastBuilder: (msg) => CustomToast(msg: msg), loadingBuilder: (msg) => LoadingWidget(msg: msg), - builder: (context, child) { - final uiScale = Pref.uiScale; - final mediaQuery = MediaQuery.of(context); - final textScaler = TextScaler.linear(Pref.defaultTextScale); - if (uiScale != 1.0) { - child = MediaQuery( - data: mediaQuery.copyWith( - textScaler: textScaler, - size: mediaQuery.size / uiScale, - padding: mediaQuery.padding / uiScale, - viewInsets: mediaQuery.viewInsets / uiScale, - viewPadding: mediaQuery.viewPadding / uiScale, - devicePixelRatio: mediaQuery.devicePixelRatio * uiScale, - ), - child: child!, - ); - } else { - child = MediaQuery( - data: mediaQuery.copyWith(textScaler: textScaler), - child: child!, - ); - } - if (PlatformUtils.isDesktop) { - return Focus( - canRequestFocus: false, - onKeyEvent: (_, event) { - if (event.logicalKey == LogicalKeyboardKey.escape && - event is KeyDownEvent) { - _onBack(); - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: MouseBackDetector( - onTapDown: _onBack, - child: child, - ), - ); - } - return child; - }, + builder: _builder, ), navigatorObservers: [ PageUtils.routeObserver, FlutterSmartDialog.observer, ], - scrollBehavior: const MaterialScrollBehavior().copyWith( - scrollbars: false, - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.invertedStylus, - PointerDeviceKind.trackpad, - PointerDeviceKind.unknown, - if (PlatformUtils.isDesktop) PointerDeviceKind.mouse, - }, - ), + scrollBehavior: PlatformUtils.isDesktop + ? const CustomScrollBehavior(desktopDragDevices) + : null, ); } + static Widget _builder(BuildContext context, Widget? child) { + final uiScale = Pref.uiScale; + final mediaQuery = MediaQuery.of(context); + final textScaler = TextScaler.linear(Pref.defaultTextScale); + if (uiScale != 1.0) { + child = MediaQuery( + data: mediaQuery.copyWith( + textScaler: textScaler, + size: mediaQuery.size / uiScale, + padding: mediaQuery.padding / uiScale, + viewInsets: mediaQuery.viewInsets / uiScale, + viewPadding: mediaQuery.viewPadding / uiScale, + devicePixelRatio: mediaQuery.devicePixelRatio * uiScale, + ), + child: child!, + ); + } else { + child = MediaQuery( + data: mediaQuery.copyWith(textScaler: textScaler), + child: child!, + ); + } + if (PlatformUtils.isDesktop) { + return BackDetector( + onBack: _onBack, + child: child, + ); + } + return child; + } + /// from [DynamicColorBuilderState.initPlatformState] static Future initPlatformState() async { if (_light != null || _dark != null) return true; @@ -388,9 +379,10 @@ class MyApp extends StatelessWidget { class _CustomHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { - final client = super.createHttpClient(context) - // ..maxConnectionsPerHost = 32 - ..idleTimeout = const Duration(seconds: 15); + final client = super.createHttpClient(context); + // ..maxConnectionsPerHost = 32 + /// The default value is 15 seconds. + // ..idleTimeout = const Duration(seconds: 15); if (kDebugMode || Pref.badCertificateCallback) { client.badCertificateCallback = (cert, host, port) => true; } diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index 4c0a81dae..9a48e78e9 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart'; import 'package:PiliPlus/models/model_video.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; // 稍后再看, 排行榜等网页返回也使用该类 diff --git a/lib/models/model_video.dart b/lib/models/model_video.dart index 5334061c7..d65c03431 100644 --- a/lib/models/model_video.dart +++ b/lib/models/model_video.dart @@ -40,15 +40,3 @@ class PlayStat extends BaseStat { danmu = json['danmaku']; } } - -class Dimension { - int? width; - int? height; - int? rotate; - - Dimension.fromJson(Map json) { - width = json["width"]; - height = json["height"]; - rotate = json["rotate"]; - } -} diff --git a/lib/models_new/fav/fav_folder/list.dart b/lib/models_new/fav/fav_folder/list.dart index e0098dba3..16eb9ac71 100644 --- a/lib/models_new/fav/fav_folder/list.dart +++ b/lib/models_new/fav/fav_folder/list.dart @@ -17,12 +17,8 @@ class FavFolderInfo { int? favState; int mediaCount; int? viewCount; - int? vt; bool? isTop; - dynamic recentFav; - int? playSwitch; int? type; - String? link; String? bvid; FavFolderInfo({ @@ -42,12 +38,8 @@ class FavFolderInfo { this.favState, this.mediaCount = 0, this.viewCount, - this.vt, this.isTop, - this.recentFav, - this.playSwitch, this.type, - this.link, this.bvid, }); @@ -70,12 +62,8 @@ class FavFolderInfo { favState: json['fav_state'] as int?, mediaCount: json['media_count'] as int? ?? 0, viewCount: json['view_count'] as int?, - vt: json['vt'] as int?, isTop: json['is_top'] as bool?, - recentFav: json['recent_fav'] as dynamic, - playSwitch: json['play_switch'] as int?, type: json['type'] as int?, - link: json['link'] as String?, bvid: json['bvid'] as String?, ); } diff --git a/lib/models_new/media_list/dimension.dart b/lib/models_new/media_list/dimension.dart deleted file mode 100644 index c2fba44eb..000000000 --- a/lib/models_new/media_list/dimension.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Dimension { - int? width; - int? height; - int? rotate; - - Dimension({this.width, this.height, this.rotate}); - - factory Dimension.fromJson(Map json) => Dimension( - width: json['width'] as int?, - height: json['height'] as int?, - rotate: json['rotate'] as int?, - ); -} diff --git a/lib/models_new/media_list/ogv_info.dart b/lib/models_new/media_list/ogv_info.dart index f6bd608ee..d4d0276d4 100644 --- a/lib/models_new/media_list/ogv_info.dart +++ b/lib/models_new/media_list/ogv_info.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/models_new/media_list/dimension.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; class OgvInfo { int? epid; diff --git a/lib/models_new/media_list/page.dart b/lib/models_new/media_list/page.dart index 874ac740c..a85b978ed 100644 --- a/lib/models_new/media_list/page.dart +++ b/lib/models_new/media_list/page.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/models_new/media_list/dimension.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; class Page { int? id; diff --git a/lib/models_new/pgc/pgc_info_model/dimension.dart b/lib/models_new/pgc/pgc_info_model/dimension.dart deleted file mode 100644 index 6fda48974..000000000 --- a/lib/models_new/pgc/pgc_info_model/dimension.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Dimension { - int? height; - int? rotate; - int? width; - - Dimension({this.height, this.rotate, this.width}); - - factory Dimension.fromJson(Map json) => Dimension( - height: json['height'] as int?, - rotate: json['rotate'] as int?, - width: json['width'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/episode.dart b/lib/models_new/pgc/pgc_info_model/episode.dart index 0a1ecab12..32740790f 100644 --- a/lib/models_new/pgc/pgc_info_model/episode.dart +++ b/lib/models_new/pgc/pgc_info_model/episode.dart @@ -1,7 +1,7 @@ import 'package:PiliPlus/models_new/pgc/pgc_info_model/badge_info.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/dimension.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/rights.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/skip.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart' show BaseEpisodeItem; diff --git a/lib/models_new/video/video_detail/arc.dart b/lib/models_new/video/video_detail/arc.dart index 7492910bd..f19931938 100644 --- a/lib/models_new/video/video_detail/arc.dart +++ b/lib/models_new/video/video_detail/arc.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/models/model_owner.dart'; -import 'package:PiliPlus/models/model_video.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/rights.dart'; import 'package:PiliPlus/models_new/video/video_detail/stat.dart'; diff --git a/lib/models_new/video/video_detail/dimension.dart b/lib/models_new/video/video_detail/dimension.dart index c2fba44eb..a60d8c800 100644 --- a/lib/models_new/video/video_detail/dimension.dart +++ b/lib/models_new/video/video_detail/dimension.dart @@ -1,13 +1,11 @@ class Dimension { int? width; int? height; - int? rotate; - Dimension({this.width, this.height, this.rotate}); + Dimension({this.width, this.height}); factory Dimension.fromJson(Map json) => Dimension( width: json['width'] as int?, height: json['height'] as int?, - rotate: json['rotate'] as int?, ); } diff --git a/lib/models_new/video/video_detail/page.dart b/lib/models_new/video/video_detail/page.dart index ba2d35725..ce2d90468 100644 --- a/lib/models_new/video/video_detail/page.dart +++ b/lib/models_new/video/video_detail/page.dart @@ -7,7 +7,6 @@ class Part extends BaseEpisodeItem { String? part; int? duration; String? vid; - String? weblink; Dimension? dimension; int? ctime; String? firstFrame; @@ -19,7 +18,6 @@ class Part extends BaseEpisodeItem { this.part, this.duration, this.vid, - this.weblink, this.dimension, this.ctime, this.firstFrame, @@ -33,7 +31,6 @@ class Part extends BaseEpisodeItem { part: json['part'] as String?, duration: json['duration'] as int?, vid: json['vid'] as String?, - weblink: json['weblink'] as String?, dimension: json['dimension'] == null ? null : Dimension.fromJson(json['dimension'] as Map), diff --git a/lib/models_new/video/video_stein_edgeinfo/dimension.dart b/lib/models_new/video/video_stein_edgeinfo/dimension.dart deleted file mode 100644 index 8e89e5b6e..000000000 --- a/lib/models_new/video/video_stein_edgeinfo/dimension.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Dimension { - int? width; - int? height; - int? rotate; - String? sar; - - Dimension({this.width, this.height, this.rotate, this.sar}); - - factory Dimension.fromJson(Map json) => Dimension( - width: json['width'] as int?, - height: json['height'] as int?, - rotate: json['rotate'] as int?, - sar: json['sar'] as String?, - ); -} diff --git a/lib/models_new/video/video_stein_edgeinfo/edges.dart b/lib/models_new/video/video_stein_edgeinfo/edges.dart index 1a9001e47..4d7a93424 100644 --- a/lib/models_new/video/video_stein_edgeinfo/edges.dart +++ b/lib/models_new/video/video_stein_edgeinfo/edges.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/dimension.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/question.dart'; import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/skin.dart'; diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart index 28797c5f5..1a9571fa4 100644 --- a/lib/pages/about/view.dart +++ b/lib/pages/about/view.dart @@ -59,10 +59,12 @@ class _AboutPageState extends State { super.dispose(); } - Future getCacheSize() async { - cacheSize.value = CacheManager.formatSize( - await CacheManager.loadApplicationCache(), - ); + void getCacheSize() { + CacheManager.loadApplicationCache().then((res) { + if (mounted) { + cacheSize.value = CacheManager.formatSize(res); + } + }); } void _showDialog() => showDialog( @@ -103,20 +105,18 @@ class _AboutPageState extends State { children: [ GestureDetector( onTap: () { - _pressCount++; - if (_pressCount == 5) { + if (++_pressCount == 5) { _pressCount = 0; _showDialog(); } }, onSecondaryTap: PlatformUtils.isDesktop ? _showDialog : null, - child: ExcludeSemantics( - child: Image.asset( - width: 150, - height: 150, - cacheWidth: 150.cacheSize(context), - 'assets/images/logo/logo.png', - ), + child: Image.asset( + width: 150, + height: 150, + excludeFromSemantics: true, + cacheWidth: 150.cacheSize(context), + 'assets/images/logo/logo.png', ), ), ListTile( diff --git a/lib/pages/common/publish/common_rich_text_pub_page.dart b/lib/pages/common/publish/common_rich_text_pub_page.dart index 51934e95f..a870d69d7 100644 --- a/lib/pages/common/publish/common_rich_text_pub_page.dart +++ b/lib/pages/common/publish/common_rich_text_pub_page.dart @@ -57,11 +57,16 @@ abstract class CommonRichTextPubPageState int get limit => widget.imageLengthLimit ?? 9; @override - late final RichTextEditingController editController = - RichTextEditingController( - items: widget.items, - onMention: onMention, - ); + late final RichTextEditingController editController; + + @override + void initState() { + super.initState(); + editController = RichTextEditingController( + items: widget.items, + onMention: onMention, + ); + } @override void initPubState() { diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 79ca0498f..2d8331a2a 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -127,8 +127,10 @@ class _FavDetailPageState extends State with GridMixin { Widget _buildHeader(bool enableMultiSelect, ThemeData theme) { return SliverAppBar.medium( + leadingWidth: enableMultiSelect ? 125 : null, leading: enableMultiSelect ? Row( + mainAxisSize: .min, children: [ IconButton( tooltip: '取消', diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 4582f3b69..a7923578d 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,8 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; -import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; @@ -77,79 +75,18 @@ class _HomePageState extends State ); } - Widget searchBarAndUser(ThemeData theme) { - return Row( + Widget customAppBar(ThemeData theme) { + const height = 52.0; + const padding = EdgeInsets.fromLTRB(14, 6, 14, 0); + final child = Row( children: [ searchBar(theme), const SizedBox(width: 4), - Obx( - () => _homeController.accountService.isLogin.value - ? msgBadge(_mainController) - : const SizedBox.shrink(), - ), + msgBadge(_mainController), const SizedBox(width: 8), - Semantics( - label: "我的", - child: Obx( - () => _homeController.accountService.isLogin.value - ? Stack( - clipBehavior: Clip.none, - children: [ - NetworkImgLayer( - type: ImageType.avatar, - width: 34, - height: 34, - src: _homeController.accountService.face.value, - ), - Positioned.fill( - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: _mainController.toMinePage, - splashColor: theme.colorScheme.primaryContainer - .withValues(alpha: 0.3), - customBorder: const CircleBorder(), - ), - ), - ), - Positioned( - right: -6, - bottom: -6, - child: Obx( - () => MineController.anonymity.value - ? IgnorePointer( - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: - theme.colorScheme.secondaryContainer, - shape: BoxShape.circle, - ), - child: Icon( - size: 16, - MdiIcons.incognito, - color: theme - .colorScheme - .onSecondaryContainer, - ), - ), - ) - : const SizedBox.shrink(), - ), - ), - ], - ) - : defaultUser( - theme: theme, - onPressed: _mainController.toMinePage, - ), - ), - ), + userAvatar(theme: theme, mainController: _mainController), ], ); - } - - Widget customAppBar(ThemeData theme) { if (_homeController.searchBar case final searchBar?) { return Obx(() { final showSearchBar = searchBar.value; @@ -159,39 +96,39 @@ class _HomePageState extends State child: AnimatedContainer( curve: Curves.easeInOutCubicEmphasized, duration: const Duration(milliseconds: 500), - height: showSearchBar ? 52 : 0, - padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), - child: searchBarAndUser(theme), + height: showSearchBar ? height : 0, + padding: padding, + child: child, ), ); }); } else { return Container( - height: 52, - padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), - child: searchBarAndUser(theme), + height: height, + padding: padding, + child: child, ); } } Widget searchBar(ThemeData theme) { + const borderRadius = BorderRadius.all(Radius.circular(25)); return Expanded( child: SizedBox( height: 44, child: Material( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: borderRadius, color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05), child: InkWell( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: borderRadius, splashColor: theme.colorScheme.primaryContainer.withValues( alpha: 0.3, ), onTap: () => Get.toNamed( '/search', - parameters: { - if (_homeController.enableSearchWord) - 'hintText': _homeController.defaultSearch.value, - }, + parameters: _homeController.enableSearchWord + ? {'hintText': _homeController.defaultSearch.value} + : null, ), child: Row( children: [ @@ -222,60 +159,109 @@ class _HomePageState extends State } } -Widget defaultUser({ +Widget userAvatar({ required ThemeData theme, - required VoidCallback onPressed, + required MainController mainController, }) { - return SizedBox( - width: 38, - height: 38, - child: IconButton( - tooltip: '点击登录', - style: ButtonStyle( - padding: const WidgetStatePropertyAll(EdgeInsets.zero), - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.onInverseSurface, - ), - ), - onPressed: onPressed, - icon: Icon( - Icons.person_rounded, - size: 22, - color: theme.colorScheme.primary, - ), + return Semantics( + label: "我的", + child: Obx( + () { + if (mainController.accountService.isLogin.value) { + return Stack( + clipBehavior: .none, + children: [ + NetworkImgLayer( + type: .avatar, + width: 34, + height: 34, + src: mainController.accountService.face.value, + ), + Positioned.fill( + child: Material( + type: .transparency, + child: InkWell( + onTap: mainController.toMinePage, + splashColor: theme.colorScheme.primaryContainer.withValues( + alpha: 0.3, + ), + customBorder: const CircleBorder(), + ), + ), + ), + Positioned( + right: -4, + bottom: -4, + child: Obx( + () => MineController.anonymity.value + ? IgnorePointer( + child: Container( + padding: const .all(2), + decoration: BoxDecoration( + shape: .circle, + color: theme.colorScheme.secondaryContainer, + ), + child: Icon( + size: 14, + MdiIcons.incognito, + color: theme.colorScheme.onSecondaryContainer, + ), + ), + ) + : const SizedBox.shrink(), + ), + ), + ], + ); + } + return SizedBox( + width: 38, + height: 38, + child: IconButton( + tooltip: '点击登录', + style: IconButton.styleFrom( + padding: .zero, + backgroundColor: theme.colorScheme.onInverseSurface, + ), + onPressed: mainController.toMinePage, + icon: Icon( + Icons.person_rounded, + size: 22, + color: theme.colorScheme.primary, + ), + ), + ); + }, ), ); } Widget msgBadge(MainController mainController) { - void toWhisper() { - mainController.msgUnReadCount.value = ''; - mainController.lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch; - Get.toNamed('/whisper'); - } - - final msgUnReadCount = mainController.msgUnReadCount.value; - return GestureDetector( - onTap: toWhisper, - child: Badge( - isLabelVisible: - mainController.msgBadgeMode != DynamicBadgeMode.hidden && - msgUnReadCount.isNotEmpty, - alignment: mainController.msgBadgeMode == DynamicBadgeMode.number - ? const Alignment(0, -0.5) - : const Alignment(0.5, -0.5), - label: - mainController.msgBadgeMode == DynamicBadgeMode.number && - msgUnReadCount.isNotEmpty - ? Text(msgUnReadCount) - : null, - child: IconButton( - tooltip: '消息', - onPressed: toWhisper, - icon: const Icon( - Icons.notifications_none, - ), - ), - ), + return Obx( + () { + if (mainController.accountService.isLogin.value) { + final count = mainController.msgUnReadCount.value; + final isNumBadge = mainController.msgBadgeMode == .number; + return IconButton( + tooltip: '消息', + onPressed: () { + mainController + ..msgUnReadCount.value = '' + ..lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch; + Get.toNamed('/whisper'); + }, + icon: Badge( + isLabelVisible: + mainController.msgBadgeMode != .hidden && count.isNotEmpty, + alignment: isNumBadge + ? const Alignment(0.0, -0.85) + : const Alignment(1.0, -0.85), + label: isNumBadge && count.isNotEmpty ? Text(count) : null, + child: const Icon(Icons.notifications_none), + ), + ); + } + return const SizedBox.shrink(); + }, ); } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index df4315229..5ab5f63a3 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -288,9 +288,14 @@ class LiveRoomController extends GetxController { ), ), TextButton( - onPressed: () => Get - ..back() - ..back(), + onPressed: () { + if (plPlayerController.isDesktopPip) { + plPlayerController.exitDesktopPip(); + } + Get + ..back() + ..back(); + }, child: const Text('退出'), ), ], diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 5bdc1939b..10bb2201a 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,14 +1,12 @@ import 'dart:io'; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/tabs.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/nav_bar_config.dart'; import 'package:PiliPlus/pages/home/view.dart'; import 'package:PiliPlus/pages/main/controller.dart'; -import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; @@ -23,7 +21,6 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; @@ -34,7 +31,7 @@ class MainApp extends StatefulWidget { State createState() => _MainAppState(); } -class _MainAppState extends State +class _MainAppState extends PopScopeState with RouteAware, WidgetsBindingObserver, WindowListener, TrayListener { final _mainController = Get.put(MainController()); late final _setting = GStorage.setting; @@ -234,7 +231,7 @@ class _MainAppState extends State await trayManager.setContextMenu(trayMenu); } - void onBack() { + static void _onBack() { if (Platform.isAndroid) { Utils.channel.invokeMethod('back'); } else { @@ -244,13 +241,8 @@ class _MainAppState extends State late bool useBottomNav; - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final padding = MediaQuery.viewPaddingOf(context); - useBottomNav = - !_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait; - Widget? bottomNav = useBottomNav + Widget? get _bottomNav { + return useBottomNav ? _mainController.navigationBars.length > 1 ? _mainController.enableMYBar ? Obx( @@ -279,7 +271,7 @@ class _MainAppState extends State iconSize: 16, selectedFontSize: 12, unselectedFontSize: 12, - type: BottomNavigationBarType.fixed, + type: .fixed, items: _mainController.navigationBars .map( (e) => BottomNavigationBarItem( @@ -296,151 +288,151 @@ class _MainAppState extends State ) : null : null; - return PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (_mainController.directExitOnBack) { - onBack(); - } else { - if (_mainController.selectedIndex.value != 0) { - _mainController - ..setIndex(0) - ..bottomBar?.value = true - ..setSearchBar(); - } else { - onBack(); - } - } - }, - child: AnnotatedRegion( - value: SystemUiOverlayStyle( - systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: theme.brightness.reverse, - ), - child: Scaffold( - extendBody: true, - resizeToAvoidBottomInset: false, - appBar: AppBar(toolbarHeight: 0), - body: Padding( - padding: EdgeInsets.only( - left: useBottomNav ? padding.left : 0.0, - right: padding.right, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (!useBottomNav) ...[ - _mainController.navigationBars.length > 1 - ? context.isTablet && _mainController.optTabletNav - ? Column( - children: [ - const SizedBox(height: 25), - userAndSearchVertical(theme), - const Spacer(flex: 2), - Expanded( - flex: 5, - child: SizedBox( - width: 130, - child: Obx( - () => NavigationDrawer( - backgroundColor: Colors.transparent, - tilePadding: - const EdgeInsets.symmetric( - vertical: 5, - horizontal: 12, - ), - indicatorShape: - const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(16), - ), - ), - onDestinationSelected: - _mainController.setIndex, - selectedIndex: _mainController - .selectedIndex - .value, - children: _mainController - .navigationBars - .map( - (e) => - NavigationDrawerDestination( - label: Text(e.label), - icon: _buildIcon( - type: e, - ), - selectedIcon: _buildIcon( - type: e, - selected: true, - ), - ), - ) - .toList(), + } + + @override + void onPopInvokedWithResult(bool didPop, Object? result) { + if (_mainController.directExitOnBack) { + _onBack(); + } else { + if (_mainController.selectedIndex.value != 0) { + _mainController + ..setIndex(0) + ..bottomBar?.value = true + ..setSearchBar(); + } else { + _onBack(); + } + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final padding = MediaQuery.viewPaddingOf(context); + useBottomNav = + !_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait; + final bottomNav = _bottomNav; + return AnnotatedRegion( + value: SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: theme.brightness.reverse, + ), + child: Scaffold( + extendBody: true, + resizeToAvoidBottomInset: false, + appBar: AppBar(toolbarHeight: 0), + body: Padding( + padding: EdgeInsets.only( + left: useBottomNav ? padding.left : 0.0, + right: padding.right, + ), + child: Row( + mainAxisAlignment: .center, + children: [ + if (!useBottomNav) ...[ + _mainController.navigationBars.length > 1 + ? context.isTablet && _mainController.optTabletNav + ? Column( + children: [ + const SizedBox(height: 25), + userAndSearchVertical(theme), + const Spacer(flex: 2), + Expanded( + flex: 5, + child: SizedBox( + width: 130, + child: Obx( + () => NavigationDrawer( + backgroundColor: Colors.transparent, + tilePadding: const .symmetric( + vertical: 5, + horizontal: 12, ), + indicatorShape: + const RoundedRectangleBorder( + borderRadius: .all( + .circular(16), + ), + ), + onDestinationSelected: + _mainController.setIndex, + selectedIndex: + _mainController.selectedIndex.value, + children: _mainController.navigationBars + .map( + (e) => + NavigationDrawerDestination( + label: Text(e.label), + icon: _buildIcon(type: e), + selectedIcon: _buildIcon( + type: e, + selected: true, + ), + ), + ) + .toList(), ), ), ), - ], - ) - : Obx( - () => NavigationRail( - groupAlignment: 0.5, - selectedIndex: - _mainController.selectedIndex.value, - onDestinationSelected: - _mainController.setIndex, - labelType: NavigationRailLabelType.selected, - leading: userAndSearchVertical(theme), - destinations: _mainController.navigationBars - .map( - (e) => NavigationRailDestination( - label: Text(e.label), - icon: _buildIcon(type: e), - selectedIcon: _buildIcon( - type: e, - selected: true, - ), - ), - ) - .toList(), ), - ) - : Container( - padding: const EdgeInsets.only(top: 10), - width: 80, - child: userAndSearchVertical(theme), - ), - VerticalDivider( - width: 1, - endIndent: padding.bottom, - color: theme.colorScheme.outline.withValues(alpha: 0.06), - ), - ], - Expanded( - child: _mainController.mainTabBarView - ? CustomTabBarView( - scrollDirection: useBottomNav - ? Axis.horizontal - : Axis.vertical, - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.controller, - children: _mainController.navigationBars - .map((i) => i.page) - .toList(), - ) - : PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.controller, - children: _mainController.navigationBars - .map((i) => i.page) - .toList(), - ), + ], + ) + : Obx( + () => NavigationRail( + groupAlignment: 0.5, + selectedIndex: + _mainController.selectedIndex.value, + onDestinationSelected: _mainController.setIndex, + labelType: .selected, + leading: userAndSearchVertical(theme), + destinations: _mainController.navigationBars + .map( + (e) => NavigationRailDestination( + label: Text(e.label), + icon: _buildIcon(type: e), + selectedIcon: _buildIcon( + type: e, + selected: true, + ), + ), + ) + .toList(), + ), + ) + : Container( + width: 80, + padding: const .only(top: 10), + child: userAndSearchVertical(theme), + ), + VerticalDivider( + width: 1, + endIndent: padding.bottom, + color: theme.colorScheme.outline.withValues(alpha: 0.06), ), ], - ), + Expanded( + child: _mainController.mainTabBarView + ? CustomTabBarView( + scrollDirection: useBottomNav ? .horizontal : .vertical, + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.controller, + children: _mainController.navigationBars + .map((i) => i.page) + .toList(), + ) + : PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.controller, + children: _mainController.navigationBars + .map((i) => i.page) + .toList(), + ), + ), + ], ), - bottomNavigationBar: _buildBottom(bottomNav), ), + bottomNavigationBar: _buildBottom(bottomNav), ), ); } @@ -461,22 +453,18 @@ class _MainAppState extends State return bottomNav; } - Widget _buildIcon({ - required NavigationBarType type, - bool selected = false, - }) { + Widget _buildIcon({required NavigationBarType type, bool selected = false}) { final icon = selected ? type.selectIcon : type.icon; - return type == NavigationBarType.dynamics + return type == .dynamics ? Obx( () { final dynCount = _mainController.dynCount.value; return Badge( isLabelVisible: dynCount > 0, - label: - _mainController.dynamicBadgeMode == DynamicBadgeMode.number + label: _mainController.dynamicBadgeMode == .number ? Text(dynCount.toString()) : null, - padding: const EdgeInsets.symmetric(horizontal: 6), + padding: const .symmetric(horizontal: 6), child: icon, ); }, @@ -487,69 +475,9 @@ class _MainAppState extends State Widget userAndSearchVertical(ThemeData theme) { return Column( children: [ - Semantics( - label: "我的", - child: Obx( - () => _mainController.accountService.isLogin.value - ? Stack( - clipBehavior: Clip.none, - children: [ - NetworkImgLayer( - type: ImageType.avatar, - width: 34, - height: 34, - src: _mainController.accountService.face.value, - ), - Positioned.fill( - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: _mainController.toMinePage, - splashColor: theme.colorScheme.primaryContainer - .withValues(alpha: 0.3), - customBorder: const CircleBorder(), - ), - ), - ), - Positioned( - right: -6, - bottom: -6, - child: Obx( - () => MineController.anonymity.value - ? IgnorePointer( - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: - theme.colorScheme.secondaryContainer, - shape: BoxShape.circle, - ), - child: Icon( - size: 16, - MdiIcons.incognito, - color: theme - .colorScheme - .onSecondaryContainer, - ), - ), - ) - : const SizedBox.shrink(), - ), - ), - ], - ) - : defaultUser( - theme: theme, - onPressed: _mainController.toMinePage, - ), - ), - ), + userAvatar(theme: theme, mainController: _mainController), const SizedBox(height: 8), - Obx( - () => _mainController.accountService.isLogin.value - ? msgBadge(_mainController) - : const SizedBox.shrink(), - ), + msgBadge(_mainController), IconButton( tooltip: '搜索', icon: const Icon( diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 7c6bc27de..1c582ebc9 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -5,9 +5,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/nav_bar_config.dart'; -import 'package:PiliPlus/models/user/info.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/home/view.dart'; @@ -70,16 +68,17 @@ class _MediaPageState extends CommonPageState return onBuild( Column( children: [ - const SizedBox(height: 10), - _buildHeaderActions, - const SizedBox(height: 10), + Padding( + padding: const .symmetric(vertical: 10), + child: _buildHeaderActions, + ), Expanded( child: Material( - type: MaterialType.transparency, + type: .transparency, child: refreshIndicator( onRefresh: controller.onRefresh, child: ListView( - padding: const EdgeInsets.only(bottom: 100), + padding: const .only(bottom: 100), controller: controller.scrollController, physics: const AlwaysScrollableScrollPhysics(), children: [ @@ -102,7 +101,7 @@ class _MediaPageState extends CommonPageState Widget _buildActions(Color primary) { return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: .spaceEvenly, children: controller.list .map( (e) => Flexible( @@ -115,14 +114,10 @@ class _MediaPageState extends CommonPageState aspectRatio: 1, child: Column( spacing: 6, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: .min, + mainAxisAlignment: .center, children: [ - Icon( - size: e.size, - e.icon, - color: primary, - ), + Icon(size: e.size, e.icon, color: primary), Text( e.title, style: const TextStyle(fontSize: 13), @@ -139,9 +134,12 @@ class _MediaPageState extends CommonPageState } Widget get _buildHeaderActions { + const iconSize = 22.0; + const padding = EdgeInsets.all(8); + const style = ButtonStyle(tapTargetSize: .shrinkWrap); return Row( spacing: 5, - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: .end, children: [ if (widget.showBackBtn) const Expanded( @@ -155,11 +153,9 @@ class _MediaPageState extends CommonPageState ), if (!_mainController.hasHome) ...[ IconButton( - iconSize: 22, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), + iconSize: iconSize, + padding: padding, + style: style, tooltip: '搜索', onPressed: () => Get.toNamed('/search'), icon: const Icon(Icons.search), @@ -170,11 +166,9 @@ class _MediaPageState extends CommonPageState () { final anonymity = MineController.anonymity.value; return IconButton( - iconSize: 22, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), + iconSize: iconSize, + padding: padding, + style: style, tooltip: "${anonymity ? '退出' : '进入'}无痕模式", onPressed: MineController.onChangeAnonymity, icon: anonymity @@ -184,11 +178,9 @@ class _MediaPageState extends CommonPageState }, ), IconButton( - iconSize: 22, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), + iconSize: iconSize, + padding: padding, + style: style, tooltip: '设置账号模式', onPressed: () => LoginPageController.switchAccountDialog(context), icon: const Icon(Icons.switch_account_outlined), @@ -196,11 +188,9 @@ class _MediaPageState extends CommonPageState Obx( () { return IconButton( - iconSize: 22, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), + iconSize: iconSize, + padding: padding, + style: style, tooltip: '切换至${controller.nextThemeType.desc}主题', onPressed: controller.onChangeTheme, icon: controller.themeType.value.icon, @@ -208,11 +198,9 @@ class _MediaPageState extends CommonPageState }, ), IconButton( - iconSize: 22, - padding: const EdgeInsets.all(8), - style: const ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), + iconSize: iconSize, + padding: padding, + style: style, tooltip: '设置', onPressed: () => Get.toNamed('/setting', preventDuplicates: false), icon: const Icon(Icons.settings_outlined), @@ -240,8 +228,8 @@ class _MediaPageState extends CommonPageState color: secondary, ); return Obx(() { - final UserInfoData userInfo = controller.userInfo.value; - final LevelInfo? levelInfo = userInfo.levelInfo; + final userInfo = controller.userInfo.value; + final levelInfo = userInfo.levelInfo; final hasLevel = levelInfo != null; final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0; final userStat = controller.userStat.value; @@ -249,7 +237,7 @@ class _MediaPageState extends CommonPageState mainAxisSize: MainAxisSize.min, children: [ GestureDetector( - behavior: HitTestBehavior.opaque, + behavior: .opaque, onTap: controller.onLogin, onLongPress: () { Feedback.forLongPress(context); @@ -259,16 +247,16 @@ class _MediaPageState extends CommonPageState ? null : () => controller.onLogin(true), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: .min, children: [ const SizedBox(width: 20), userInfo.face != null ? Stack( - clipBehavior: Clip.none, + clipBehavior: .none, children: [ NetworkImgLayer( src: userInfo.face, - type: ImageType.avatar, + type: .avatar, width: 55, height: 55, ), @@ -297,9 +285,9 @@ class _MediaPageState extends CommonPageState const SizedBox(width: 16), Expanded( child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: .min, + mainAxisAlignment: .center, + crossAxisAlignment: .start, children: [ Row( spacing: 6, @@ -313,6 +301,8 @@ class _MediaPageState extends CommonPageState ? theme.colorScheme.vipColor : null, ), + maxLines: 1, + overflow: .ellipsis, ), ), Image.asset( @@ -376,7 +366,7 @@ class _MediaPageState extends CommonPageState ), const SizedBox(height: 10), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: .spaceEvenly, children: [ _btn( count: userStat.dynamicCount, @@ -422,14 +412,14 @@ class _MediaPageState extends CommonPageState child: AspectRatio( aspectRatio: 1, child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + spacing: 4, + mainAxisSize: .min, + mainAxisAlignment: .center, children: [ Text( count?.toString() ?? '-', style: countStyle, ), - const SizedBox(height: 4), Text( name, style: labelStyle, @@ -466,7 +456,7 @@ class _MediaPageState extends CommonPageState text: '我的收藏 ', style: TextStyle( fontSize: theme.textTheme.titleMedium!.fontSize, - fontWeight: FontWeight.bold, + fontWeight: .bold, ), ), if (controller.favFolderCount != null) @@ -516,19 +506,17 @@ class _MediaPageState extends CommonPageState return SizedBox( height: 200, child: ListView.separated( - padding: const EdgeInsets.only(left: 20, top: 12, right: 20), + padding: const .only(left: 20, top: 10, right: 20), itemCount: response.list.length + (flag ? 1 : 0), itemBuilder: (context, index) { if (flag && index == favFolderList.length) { return Padding( - padding: const EdgeInsets.only(bottom: 35), + padding: const .only(bottom: 35), child: Center( child: IconButton( tooltip: '查看更多', style: ButtonStyle( - padding: const WidgetStatePropertyAll( - EdgeInsets.zero, - ), + padding: const WidgetStatePropertyAll(.zero), backgroundColor: WidgetStatePropertyAll( theme.colorScheme.secondaryContainer.withValues( alpha: 0.5, @@ -560,8 +548,8 @@ class _MediaPageState extends CommonPageState ); } }, - scrollDirection: Axis.horizontal, - separatorBuilder: (context, index) => const SizedBox(width: 14), + scrollDirection: .horizontal, + separatorBuilder: (_, _) => const SizedBox(width: 14), ), ); }, @@ -571,7 +559,7 @@ class _MediaPageState extends CommonPageState child: Center( child: Text( errMsg ?? '', - textAlign: TextAlign.center, + textAlign: .center, ), ), ), diff --git a/lib/pages/mine/widgets/item.dart b/lib/pages/mine/widgets/item.dart index 356e5115c..cc21dc10b 100644 --- a/lib/pages/mine/widgets/item.dart +++ b/lib/pages/mine/widgets/item.dart @@ -42,7 +42,7 @@ class FavFolderItem extends StatelessWidget { color: theme.colorScheme.onInverseSurface.withValues( alpha: 0.4, ), - offset: const Offset(4, -12), + offset: const Offset(6, -8), blurRadius: 0.0, spreadRadius: 0.0, ), diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 66a1140b0..048d9cca5 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -31,8 +31,8 @@ class _RcmdPageState extends CommonPageState super.build(context); return onBuild( Container( - clipBehavior: Clip.hardEdge, - margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + clipBehavior: .hardEdge, + margin: const .symmetric(horizontal: StyleString.safeSpace), decoration: const BoxDecoration(borderRadius: StyleString.mdRadius), child: refreshIndicator( onRefresh: controller.onRefresh, @@ -41,10 +41,7 @@ class _RcmdPageState extends CommonPageState physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: const EdgeInsets.only( - top: StyleString.cardSpace, - bottom: 100, - ), + padding: const .only(top: StyleString.cardSpace, bottom: 100), sliver: Obx(() => _buildBody(controller.loadingState.value)), ), ], @@ -87,7 +84,7 @@ class _RcmdPageState extends CommonPageState ), child: Text( '上次看到这里\n点击刷新', - textAlign: TextAlign.center, + textAlign: .center, style: TextStyle( color: Theme.of( context, diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 47ed404b1..5fed45165 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -44,7 +44,7 @@ class _SettingPageState extends State { final RxBool _noAccount = Accounts.account.isEmpty.obs; late bool _isPortrait; - final List<_SettingsModel> _items = const [ + static const List<_SettingsModel> _items = [ _SettingsModel( type: SettingType.privacySetting, subtitle: '黑名单、无痕模式', diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index b0b27d3be..6f0c7c403 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -91,6 +91,17 @@ class _CdnSelectDialogState extends State { void initState() { _cdnSpeedTest = Pref.cdnSpeedTest; if (_cdnSpeedTest) { + _dio = + Dio( + BaseOptions( + connectTimeout: const Duration(seconds: 15), + receiveTimeout: const Duration(seconds: 15), + ), + ) + ..options.headers = { + 'user-agent': UaType.pc.ua, + 'referer': HttpString.baseUrl, + }; final length = CDNService.values.length; _cdnResList = List.generate( length, @@ -156,11 +167,7 @@ class _CdnSelectDialogState extends State { } } - late final _dio = Dio() - ..options.headers = { - 'user-agent': UaType.pc.ua, - 'referer': HttpString.baseUrl, - }; + late final Dio _dio; Future _measureDownloadSpeed(String url, int index) async { const maxSize = 8 * 1024 * 1024; diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index a0bcd8298..db32c55fc 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -1456,7 +1456,7 @@ class VideoDetailController extends GetxController } RxList subtitles = RxList(); - late final Map vttSubtitles = {}; + final Map vttSubtitles = {}; late final RxInt vttSubtitlesIndex = (-1).obs; late final RxBool showVP = true.obs; late final RxList viewPointList = [].obs; @@ -1471,19 +1471,21 @@ class VideoDetailController extends GetxController return; } - Future setSub(String subtitle) async { + Future setSub(({bool isData, String id}) subtitle) async { final sub = subtitles[index - 1]; await plPlayerController.videoPlayerController?.setSubtitleTrack( - SubtitleTrack.data( - subtitle, - title: sub.lanDoc, - language: sub.lan, + SubtitleTrack( + subtitle.id, + sub.lanDoc, + sub.lan, + uri: !subtitle.isData, + data: subtitle.isData, ), ); vttSubtitlesIndex.value = index; } - String? subtitle = vttSubtitles[index - 1]; + ({bool isData, String id})? subtitle = vttSubtitles[index - 1]; if (subtitle != null) { await setSub(subtitle); } else { @@ -1491,8 +1493,9 @@ class VideoDetailController extends GetxController subtitles[index - 1].subtitleUrl!, ); if (!isClosed && result != null) { - vttSubtitles[index - 1] = result; - await setSub(result); + final subtitle = (isData: true, id: result); + vttSubtitles[index - 1] = subtitle; + await setSub(subtitle); } } } @@ -1665,6 +1668,8 @@ class VideoDetailController extends GetxController ?..removeListener(scrollListener) ..dispose(); animController?.dispose(); + subtitles.clear(); + vttSubtitles.clear(); super.onClose(); } diff --git a/lib/pages/video/download_panel/view.dart b/lib/pages/video/download_panel/view.dart index 2447adaca..9d7f67ee0 100644 --- a/lib/pages/video/download_panel/view.dart +++ b/lib/pages/video/download_panel/view.dart @@ -324,7 +324,6 @@ class _DownloadPanelState extends State { required ugc.BaseEpisodeItem episode, }) { late String title; - String? cover; num? duration; int? pubdate; int? view; @@ -332,6 +331,11 @@ class _DownloadPanelState extends State { bool? isCharging; int? cid; + String? cover; + int? width; + int? height; + bool cacheWidth = false; + switch (episode) { case Part part: cid = part.cid; @@ -339,15 +343,27 @@ class _DownloadPanelState extends State { title = part.part ?? widget.videoDetail!.title!; duration = part.duration; pubdate = part.ctime; + if (part.dimension case final dimension?) { + width = dimension.width; + height = dimension.height; + } break; case ugc.EpisodeItem item: cid = item.cid; title = item.title!; - cover = item.arc?.pic; - duration = item.arc?.duration; - pubdate = item.arc?.pubdate; - view = item.arc?.stat?.view; - danmaku = item.arc?.stat?.danmaku; + if (item.arc case final arc?) { + cover = arc.pic; + duration = arc.duration; + pubdate = arc.pubdate; + if (arc.stat case final stat?) { + view = stat.view; + danmaku = stat.danmaku; + } + if (arc.dimension case final dimension?) { + width = dimension.width; + height = dimension.height; + } + } if (item.attribute == 8) { isCharging = true; } @@ -363,8 +379,15 @@ class _DownloadPanelState extends State { duration = item.duration == null ? null : item.duration! ~/ 1000; } pubdate = item.pubTime; + if (item.dimension case final dimension?) { + width = dimension.width; + height = dimension.height; + } break; } + if (width != null && height != null) { + cacheWidth = width <= height; + } late final primary = theme.colorScheme.primary; return Padding( @@ -401,6 +424,7 @@ class _DownloadPanelState extends State { src: cover, width: 140.8, height: 88, + cacheWidth: cacheWidth, ), if (duration != null && duration > 0) PBadge( diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index cdb043c16..3c3e830ac 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:convert'; +import 'dart:convert' show jsonDecode, utf8; import 'dart:io'; import 'dart:math'; @@ -717,34 +717,41 @@ class HeaderControlState extends State final first = file.files.first; final path = first.path; if (path != null) { - final file = File(path); - final stream = file.openRead().transform( - utf8.decoder, - ); - final buffer = StringBuffer(); - await for (final chunk in stream) { - if (!mounted) return; - buffer.write(chunk); - } - if (!mounted) return; - String sub = buffer.toString(); final name = first.name; + final length = videoDetailCtr.subtitles.length; if (name.endsWith('.json')) { + final file = File(path); + final stream = file.openRead().transform( + utf8.decoder, + ); + final buffer = StringBuffer(); + await for (final chunk in stream) { + if (!mounted) return; + buffer.write(chunk); + } + if (!mounted) return; + String sub = buffer.toString(); sub = await compute( VideoHttp.processList, jsonDecode(sub)['body'], ); if (!mounted) return; + videoDetailCtr.vttSubtitles[length] = ( + isData: true, + id: sub, + ); + } else { + videoDetailCtr.vttSubtitles[length] = ( + isData: false, + id: path, + ); } - final length = videoDetailCtr.subtitles.length; - videoDetailCtr - ..subtitles.add( - Subtitle( - lan: '', - lanDoc: name.split('.').firstOrNull ?? name, - ), - ) - ..vttSubtitles[length] = sub; + videoDetailCtr.subtitles.add( + Subtitle( + lan: '', + lanDoc: name.split('.').firstOrNull ?? name, + ), + ); await videoDetailCtr.setSubtitle(length + 1); } }