Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-22 15:51:22 +08:00
parent 92e5fae29c
commit b9b54ce4f7
34 changed files with 627 additions and 665 deletions

View File

@@ -1,4 +1,4 @@
class BuildConfig { abstract final class BuildConfig {
static const int versionCode = int.fromEnvironment( static const int versionCode = int.fromEnvironment(
'pili.code', 'pili.code',
defaultValue: 1, defaultValue: 1,

View File

@@ -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();
}
}
}

View File

@@ -34,6 +34,9 @@ class _CustomTooltipState extends State<CustomTooltip> {
final OverlayPortalController _overlayController = OverlayPortalController(); final OverlayPortalController _overlayController = OverlayPortalController();
LongPressGestureRecognizer? _longPressRecognizer; LongPressGestureRecognizer? _longPressRecognizer;
LongPressGestureRecognizer get longPressRecognizer =>
_longPressRecognizer ??= LongPressGestureRecognizer()
..onLongPress = _scheduleShowTooltip;
void _scheduleShowTooltip() { void _scheduleShowTooltip() {
_overlayController.show(); _overlayController.show();
@@ -45,9 +48,7 @@ class _CustomTooltipState extends State<CustomTooltip> {
void _handlePointerDown(PointerDownEvent event) { void _handlePointerDown(PointerDownEvent event) {
assert(mounted); assert(mounted);
(_longPressRecognizer ??= LongPressGestureRecognizer( longPressRecognizer.addPointer(event);
debugOwner: this,
)..onLongPress = _scheduleShowTooltip).addPointer(event);
} }
Widget _buildCustomTooltipOverlay(BuildContext context) { Widget _buildCustomTooltipOverlay(BuildContext context) {
@@ -80,7 +81,7 @@ class _CustomTooltipState extends State<CustomTooltip> {
@override @override
void dispose() { void dispose() {
_longPressRecognizer _longPressRecognizer
?..onLongPressCancel = null ?..onLongPress = null
..dispose(); ..dispose();
_longPressRecognizer = null; _longPressRecognizer = null;
super.dispose(); super.dispose();

View File

@@ -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<T extends StatefulWidget> extends State<T>
implements PopEntry<T> {
ModalRoute<dynamic>? _route;
@override
void onPopInvoked(bool didPop) {}
@override
late final ValueNotifier<bool> canPopNotifier;
void initCanPopNotifier() {
canPopNotifier = ValueNotifier<bool>(false);
}
@override
void initState() {
super.initState();
initCanPopNotifier();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute<dynamic>? 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();
}
}

View File

@@ -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,
);
}
}

View File

@@ -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<PointerDeviceKind> dragDevices;
}
const Set<PointerDeviceKind> desktopDragDevices = <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
PointerDeviceKind.unknown,
PointerDeviceKind.mouse,
};

View File

@@ -2,9 +2,10 @@ import 'dart:io';
import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/common/constants.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/custom_toast.dart';
import 'package:PiliPlus/common/widgets/mouse_back.dart';
import 'package:PiliPlus/common/widgets/scale_app.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/http/init.dart';
import 'package:PiliPlus/models/common/theme/theme_color_type.dart'; import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
import 'package:PiliPlus/router/app_pages.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:catcher_2/catcher_2.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show PointerDeviceKind;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
@@ -45,20 +45,7 @@ import 'package:window_manager/window_manager.dart' hide calcWindowPosition;
WebViewEnvironment? webViewEnvironment; WebViewEnvironment? webViewEnvironment;
void main() async { Future<void> _initDownPath() 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);
if (PlatformUtils.isDesktop) { if (PlatformUtils.isDesktop) {
final customDownPath = Pref.downloadPath; final customDownPath = Pref.downloadPath;
if (customDownPath != null && customDownPath.isNotEmpty) { if (customDownPath != null && customDownPath.isNotEmpty) {
@@ -86,6 +73,29 @@ void main() async {
} else { } else {
downloadPath = defDownloadPath; downloadPath = defDownloadPath;
} }
}
Future<void> _initTmpPath() async {
tmpDirPath = (await getTemporaryDirectory()).path;
}
Future<void> _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 Get
..lazyPut(AccountService.new) ..lazyPut(AccountService.new)
..lazyPut(DownloadService.new); ..lazyPut(DownloadService.new);
@@ -106,9 +116,7 @@ void main() async {
), ),
setupServiceLocator(), setupServiceLocator(),
]); ]);
} } else if (Platform.isWindows) {
if (Platform.isWindows) {
if (await WebViewEnvironment.getAvailableVersion() != null) { if (await WebViewEnvironment.getAvailableVersion() != null) {
webViewEnvironment = await WebViewEnvironment.create( webViewEnvironment = await WebViewEnvironment.create(
settings: WebViewEnvironmentSettings( settings: WebViewEnvironmentSettings(
@@ -279,66 +287,49 @@ class MyApp extends StatelessWidget {
builder: FlutterSmartDialog.init( builder: FlutterSmartDialog.init(
toastBuilder: (msg) => CustomToast(msg: msg), toastBuilder: (msg) => CustomToast(msg: msg),
loadingBuilder: (msg) => LoadingWidget(msg: msg), loadingBuilder: (msg) => LoadingWidget(msg: msg),
builder: (context, child) { builder: _builder,
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;
},
), ),
navigatorObservers: [ navigatorObservers: [
PageUtils.routeObserver, PageUtils.routeObserver,
FlutterSmartDialog.observer, FlutterSmartDialog.observer,
], ],
scrollBehavior: const MaterialScrollBehavior().copyWith( scrollBehavior: PlatformUtils.isDesktop
scrollbars: false, ? const CustomScrollBehavior(desktopDragDevices)
dragDevices: { : null,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
PointerDeviceKind.unknown,
if (PlatformUtils.isDesktop) PointerDeviceKind.mouse,
},
),
); );
} }
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] /// from [DynamicColorBuilderState.initPlatformState]
static Future<bool> initPlatformState() async { static Future<bool> initPlatformState() async {
if (_light != null || _dark != null) return true; if (_light != null || _dark != null) return true;
@@ -388,9 +379,10 @@ class MyApp extends StatelessWidget {
class _CustomHttpOverrides extends HttpOverrides { class _CustomHttpOverrides extends HttpOverrides {
@override @override
HttpClient createHttpClient(SecurityContext? context) { HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context) final client = super.createHttpClient(context);
// ..maxConnectionsPerHost = 32 // ..maxConnectionsPerHost = 32
..idleTimeout = const Duration(seconds: 15); /// The default value is 15 seconds.
// ..idleTimeout = const Duration(seconds: 15);
if (kDebugMode || Pref.badCertificateCallback) { if (kDebugMode || Pref.badCertificateCallback) {
client.badCertificateCallback = (cert, host, port) => true; client.badCertificateCallback = (cert, host, port) => true;
} }

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/model_video.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'; import 'package:PiliPlus/pages/common/multi_select/base.dart';
// 稍后再看, 排行榜等网页返回也使用该类 // 稍后再看, 排行榜等网页返回也使用该类

View File

@@ -40,15 +40,3 @@ class PlayStat extends BaseStat {
danmu = json['danmaku']; danmu = json['danmaku'];
} }
} }
class Dimension {
int? width;
int? height;
int? rotate;
Dimension.fromJson(Map<String, dynamic> json) {
width = json["width"];
height = json["height"];
rotate = json["rotate"];
}
}

View File

@@ -17,12 +17,8 @@ class FavFolderInfo {
int? favState; int? favState;
int mediaCount; int mediaCount;
int? viewCount; int? viewCount;
int? vt;
bool? isTop; bool? isTop;
dynamic recentFav;
int? playSwitch;
int? type; int? type;
String? link;
String? bvid; String? bvid;
FavFolderInfo({ FavFolderInfo({
@@ -42,12 +38,8 @@ class FavFolderInfo {
this.favState, this.favState,
this.mediaCount = 0, this.mediaCount = 0,
this.viewCount, this.viewCount,
this.vt,
this.isTop, this.isTop,
this.recentFav,
this.playSwitch,
this.type, this.type,
this.link,
this.bvid, this.bvid,
}); });
@@ -70,12 +62,8 @@ class FavFolderInfo {
favState: json['fav_state'] as int?, favState: json['fav_state'] as int?,
mediaCount: json['media_count'] as int? ?? 0, mediaCount: json['media_count'] as int? ?? 0,
viewCount: json['view_count'] as int?, viewCount: json['view_count'] as int?,
vt: json['vt'] as int?,
isTop: json['is_top'] as bool?, isTop: json['is_top'] as bool?,
recentFav: json['recent_fav'] as dynamic,
playSwitch: json['play_switch'] as int?,
type: json['type'] as int?, type: json['type'] as int?,
link: json['link'] as String?,
bvid: json['bvid'] as String?, bvid: json['bvid'] as String?,
); );
} }

View File

@@ -1,13 +0,0 @@
class Dimension {
int? width;
int? height;
int? rotate;
Dimension({this.width, this.height, this.rotate});
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
width: json['width'] as int?,
height: json['height'] as int?,
rotate: json['rotate'] as int?,
);
}

View File

@@ -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 { class OgvInfo {
int? epid; int? epid;

View File

@@ -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 { class Page {
int? id; int? id;

View File

@@ -1,13 +0,0 @@
class Dimension {
int? height;
int? rotate;
int? width;
Dimension({this.height, this.rotate, this.width});
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
height: json['height'] as int?,
rotate: json['rotate'] as int?,
width: json['width'] as int?,
);
}

View File

@@ -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/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/rights.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/skip.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' import 'package:PiliPlus/models_new/video/video_detail/episode.dart'
show BaseEpisodeItem; show BaseEpisodeItem;

View File

@@ -1,5 +1,5 @@
import 'package:PiliPlus/models/model_owner.dart'; 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/rights.dart';
import 'package:PiliPlus/models_new/video/video_detail/stat.dart'; import 'package:PiliPlus/models_new/video/video_detail/stat.dart';

View File

@@ -1,13 +1,11 @@
class Dimension { class Dimension {
int? width; int? width;
int? height; int? height;
int? rotate;
Dimension({this.width, this.height, this.rotate}); Dimension({this.width, this.height});
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension( factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
width: json['width'] as int?, width: json['width'] as int?,
height: json['height'] as int?, height: json['height'] as int?,
rotate: json['rotate'] as int?,
); );
} }

View File

@@ -7,7 +7,6 @@ class Part extends BaseEpisodeItem {
String? part; String? part;
int? duration; int? duration;
String? vid; String? vid;
String? weblink;
Dimension? dimension; Dimension? dimension;
int? ctime; int? ctime;
String? firstFrame; String? firstFrame;
@@ -19,7 +18,6 @@ class Part extends BaseEpisodeItem {
this.part, this.part,
this.duration, this.duration,
this.vid, this.vid,
this.weblink,
this.dimension, this.dimension,
this.ctime, this.ctime,
this.firstFrame, this.firstFrame,
@@ -33,7 +31,6 @@ class Part extends BaseEpisodeItem {
part: json['part'] as String?, part: json['part'] as String?,
duration: json['duration'] as int?, duration: json['duration'] as int?,
vid: json['vid'] as String?, vid: json['vid'] as String?,
weblink: json['weblink'] as String?,
dimension: json['dimension'] == null dimension: json['dimension'] == null
? null ? null
: Dimension.fromJson(json['dimension'] as Map<String, dynamic>), : Dimension.fromJson(json['dimension'] as Map<String, dynamic>),

View File

@@ -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<String, dynamic> json) => Dimension(
width: json['width'] as int?,
height: json['height'] as int?,
rotate: json['rotate'] as int?,
sar: json['sar'] as String?,
);
}

View File

@@ -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/question.dart';
import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/skin.dart'; import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/skin.dart';

View File

@@ -59,10 +59,12 @@ class _AboutPageState extends State<AboutPage> {
super.dispose(); super.dispose();
} }
Future<void> getCacheSize() async { void getCacheSize() {
cacheSize.value = CacheManager.formatSize( CacheManager.loadApplicationCache().then((res) {
await CacheManager.loadApplicationCache(), if (mounted) {
); cacheSize.value = CacheManager.formatSize(res);
}
});
} }
void _showDialog() => showDialog( void _showDialog() => showDialog(
@@ -103,20 +105,18 @@ class _AboutPageState extends State<AboutPage> {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
_pressCount++; if (++_pressCount == 5) {
if (_pressCount == 5) {
_pressCount = 0; _pressCount = 0;
_showDialog(); _showDialog();
} }
}, },
onSecondaryTap: PlatformUtils.isDesktop ? _showDialog : null, onSecondaryTap: PlatformUtils.isDesktop ? _showDialog : null,
child: ExcludeSemantics( child: Image.asset(
child: Image.asset( width: 150,
width: 150, height: 150,
height: 150, excludeFromSemantics: true,
cacheWidth: 150.cacheSize(context), cacheWidth: 150.cacheSize(context),
'assets/images/logo/logo.png', 'assets/images/logo/logo.png',
),
), ),
), ),
ListTile( ListTile(

View File

@@ -57,11 +57,16 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
int get limit => widget.imageLengthLimit ?? 9; int get limit => widget.imageLengthLimit ?? 9;
@override @override
late final RichTextEditingController editController = late final RichTextEditingController editController;
RichTextEditingController(
items: widget.items, @override
onMention: onMention, void initState() {
); super.initState();
editController = RichTextEditingController(
items: widget.items,
onMention: onMention,
);
}
@override @override
void initPubState() { void initPubState() {

View File

@@ -127,8 +127,10 @@ class _FavDetailPageState extends State<FavDetailPage> with GridMixin {
Widget _buildHeader(bool enableMultiSelect, ThemeData theme) { Widget _buildHeader(bool enableMultiSelect, ThemeData theme) {
return SliverAppBar.medium( return SliverAppBar.medium(
leadingWidth: enableMultiSelect ? 125 : null,
leading: enableMultiSelect leading: enableMultiSelect
? Row( ? Row(
mainAxisSize: .min,
children: [ children: [
IconButton( IconButton(
tooltip: '取消', tooltip: '取消',

View File

@@ -1,8 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.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/home/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart';
@@ -77,79 +75,18 @@ class _HomePageState extends State<HomePage>
); );
} }
Widget searchBarAndUser(ThemeData theme) { Widget customAppBar(ThemeData theme) {
return Row( const height = 52.0;
const padding = EdgeInsets.fromLTRB(14, 6, 14, 0);
final child = Row(
children: [ children: [
searchBar(theme), searchBar(theme),
const SizedBox(width: 4), const SizedBox(width: 4),
Obx( msgBadge(_mainController),
() => _homeController.accountService.isLogin.value
? msgBadge(_mainController)
: const SizedBox.shrink(),
),
const SizedBox(width: 8), const SizedBox(width: 8),
Semantics( userAvatar(theme: theme, mainController: _mainController),
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,
),
),
),
], ],
); );
}
Widget customAppBar(ThemeData theme) {
if (_homeController.searchBar case final searchBar?) { if (_homeController.searchBar case final searchBar?) {
return Obx(() { return Obx(() {
final showSearchBar = searchBar.value; final showSearchBar = searchBar.value;
@@ -159,39 +96,39 @@ class _HomePageState extends State<HomePage>
child: AnimatedContainer( child: AnimatedContainer(
curve: Curves.easeInOutCubicEmphasized, curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
height: showSearchBar ? 52 : 0, height: showSearchBar ? height : 0,
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), padding: padding,
child: searchBarAndUser(theme), child: child,
), ),
); );
}); });
} else { } else {
return Container( return Container(
height: 52, height: height,
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), padding: padding,
child: searchBarAndUser(theme), child: child,
); );
} }
} }
Widget searchBar(ThemeData theme) { Widget searchBar(ThemeData theme) {
const borderRadius = BorderRadius.all(Radius.circular(25));
return Expanded( return Expanded(
child: SizedBox( child: SizedBox(
height: 44, height: 44,
child: Material( child: Material(
borderRadius: const BorderRadius.all(Radius.circular(25)), borderRadius: borderRadius,
color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05), color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05),
child: InkWell( child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(25)), borderRadius: borderRadius,
splashColor: theme.colorScheme.primaryContainer.withValues( splashColor: theme.colorScheme.primaryContainer.withValues(
alpha: 0.3, alpha: 0.3,
), ),
onTap: () => Get.toNamed( onTap: () => Get.toNamed(
'/search', '/search',
parameters: { parameters: _homeController.enableSearchWord
if (_homeController.enableSearchWord) ? {'hintText': _homeController.defaultSearch.value}
'hintText': _homeController.defaultSearch.value, : null,
},
), ),
child: Row( child: Row(
children: [ children: [
@@ -222,60 +159,109 @@ class _HomePageState extends State<HomePage>
} }
} }
Widget defaultUser({ Widget userAvatar({
required ThemeData theme, required ThemeData theme,
required VoidCallback onPressed, required MainController mainController,
}) { }) {
return SizedBox( return Semantics(
width: 38, label: "我的",
height: 38, child: Obx(
child: IconButton( () {
tooltip: '点击登录', if (mainController.accountService.isLogin.value) {
style: ButtonStyle( return Stack(
padding: const WidgetStatePropertyAll(EdgeInsets.zero), clipBehavior: .none,
backgroundColor: WidgetStatePropertyAll( children: [
theme.colorScheme.onInverseSurface, NetworkImgLayer(
), type: .avatar,
), width: 34,
onPressed: onPressed, height: 34,
icon: Icon( src: mainController.accountService.face.value,
Icons.person_rounded, ),
size: 22, Positioned.fill(
color: theme.colorScheme.primary, 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) { Widget msgBadge(MainController mainController) {
void toWhisper() { return Obx(
mainController.msgUnReadCount.value = ''; () {
mainController.lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch; if (mainController.accountService.isLogin.value) {
Get.toNamed('/whisper'); final count = mainController.msgUnReadCount.value;
} final isNumBadge = mainController.msgBadgeMode == .number;
return IconButton(
final msgUnReadCount = mainController.msgUnReadCount.value; tooltip: '消息',
return GestureDetector( onPressed: () {
onTap: toWhisper, mainController
child: Badge( ..msgUnReadCount.value = ''
isLabelVisible: ..lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch;
mainController.msgBadgeMode != DynamicBadgeMode.hidden && Get.toNamed('/whisper');
msgUnReadCount.isNotEmpty, },
alignment: mainController.msgBadgeMode == DynamicBadgeMode.number icon: Badge(
? const Alignment(0, -0.5) isLabelVisible:
: const Alignment(0.5, -0.5), mainController.msgBadgeMode != .hidden && count.isNotEmpty,
label: alignment: isNumBadge
mainController.msgBadgeMode == DynamicBadgeMode.number && ? const Alignment(0.0, -0.85)
msgUnReadCount.isNotEmpty : const Alignment(1.0, -0.85),
? Text(msgUnReadCount) label: isNumBadge && count.isNotEmpty ? Text(count) : null,
: null, child: const Icon(Icons.notifications_none),
child: IconButton( ),
tooltip: '消息', );
onPressed: toWhisper, }
icon: const Icon( return const SizedBox.shrink();
Icons.notifications_none, },
),
),
),
); );
} }

View File

@@ -288,9 +288,14 @@ class LiveRoomController extends GetxController {
), ),
), ),
TextButton( TextButton(
onPressed: () => Get onPressed: () {
..back() if (plPlayerController.isDesktopPip) {
..back(), plPlayerController.exitDesktopPip();
}
Get
..back()
..back();
},
child: const Text('退出'), child: const Text('退出'),
), ),
], ],

View File

@@ -1,14 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:PiliPlus/common/constants.dart'; 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/flutter/tabs.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.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/models/common/nav_bar_config.dart';
import 'package:PiliPlus/pages/home/view.dart'; import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/main/controller.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/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
import 'package:PiliPlus/utils/app_scheme.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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.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:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@@ -34,7 +31,7 @@ class MainApp extends StatefulWidget {
State<MainApp> createState() => _MainAppState(); State<MainApp> createState() => _MainAppState();
} }
class _MainAppState extends State<MainApp> class _MainAppState extends PopScopeState<MainApp>
with RouteAware, WidgetsBindingObserver, WindowListener, TrayListener { with RouteAware, WidgetsBindingObserver, WindowListener, TrayListener {
final _mainController = Get.put(MainController()); final _mainController = Get.put(MainController());
late final _setting = GStorage.setting; late final _setting = GStorage.setting;
@@ -234,7 +231,7 @@ class _MainAppState extends State<MainApp>
await trayManager.setContextMenu(trayMenu); await trayManager.setContextMenu(trayMenu);
} }
void onBack() { static void _onBack() {
if (Platform.isAndroid) { if (Platform.isAndroid) {
Utils.channel.invokeMethod('back'); Utils.channel.invokeMethod('back');
} else { } else {
@@ -244,13 +241,8 @@ class _MainAppState extends State<MainApp>
late bool useBottomNav; late bool useBottomNav;
@override Widget? get _bottomNav {
Widget build(BuildContext context) { return useBottomNav
final theme = Theme.of(context);
final padding = MediaQuery.viewPaddingOf(context);
useBottomNav =
!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait;
Widget? bottomNav = useBottomNav
? _mainController.navigationBars.length > 1 ? _mainController.navigationBars.length > 1
? _mainController.enableMYBar ? _mainController.enableMYBar
? Obx( ? Obx(
@@ -279,7 +271,7 @@ class _MainAppState extends State<MainApp>
iconSize: 16, iconSize: 16,
selectedFontSize: 12, selectedFontSize: 12,
unselectedFontSize: 12, unselectedFontSize: 12,
type: BottomNavigationBarType.fixed, type: .fixed,
items: _mainController.navigationBars items: _mainController.navigationBars
.map( .map(
(e) => BottomNavigationBarItem( (e) => BottomNavigationBarItem(
@@ -296,151 +288,151 @@ class _MainAppState extends State<MainApp>
) )
: null : null
: null; : null;
return PopScope( }
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) { @override
if (_mainController.directExitOnBack) { void onPopInvokedWithResult(bool didPop, Object? result) {
onBack(); if (_mainController.directExitOnBack) {
} else { _onBack();
if (_mainController.selectedIndex.value != 0) { } else {
_mainController if (_mainController.selectedIndex.value != 0) {
..setIndex(0) _mainController
..bottomBar?.value = true ..setIndex(0)
..setSearchBar(); ..bottomBar?.value = true
} else { ..setSearchBar();
onBack(); } else {
} _onBack();
} }
}, }
child: AnnotatedRegion<SystemUiOverlayStyle>( }
value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent, @override
systemNavigationBarIconBrightness: theme.brightness.reverse, Widget build(BuildContext context) {
), final theme = Theme.of(context);
child: Scaffold( final padding = MediaQuery.viewPaddingOf(context);
extendBody: true, useBottomNav =
resizeToAvoidBottomInset: false, !_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait;
appBar: AppBar(toolbarHeight: 0), final bottomNav = _bottomNav;
body: Padding( return AnnotatedRegion<SystemUiOverlayStyle>(
padding: EdgeInsets.only( value: SystemUiOverlayStyle(
left: useBottomNav ? padding.left : 0.0, systemNavigationBarColor: Colors.transparent,
right: padding.right, systemNavigationBarIconBrightness: theme.brightness.reverse,
), ),
child: Row( child: Scaffold(
mainAxisAlignment: MainAxisAlignment.center, extendBody: true,
children: [ resizeToAvoidBottomInset: false,
if (!useBottomNav) ...[ appBar: AppBar(toolbarHeight: 0),
_mainController.navigationBars.length > 1 body: Padding(
? context.isTablet && _mainController.optTabletNav padding: EdgeInsets.only(
? Column( left: useBottomNav ? padding.left : 0.0,
children: [ right: padding.right,
const SizedBox(height: 25), ),
userAndSearchVertical(theme), child: Row(
const Spacer(flex: 2), mainAxisAlignment: .center,
Expanded( children: [
flex: 5, if (!useBottomNav) ...[
child: SizedBox( _mainController.navigationBars.length > 1
width: 130, ? context.isTablet && _mainController.optTabletNav
child: Obx( ? Column(
() => NavigationDrawer( children: [
backgroundColor: Colors.transparent, const SizedBox(height: 25),
tilePadding: userAndSearchVertical(theme),
const EdgeInsets.symmetric( const Spacer(flex: 2),
vertical: 5, Expanded(
horizontal: 12, flex: 5,
), child: SizedBox(
indicatorShape: width: 130,
const RoundedRectangleBorder( child: Obx(
borderRadius: BorderRadius.all( () => NavigationDrawer(
Radius.circular(16), backgroundColor: Colors.transparent,
), tilePadding: const .symmetric(
), vertical: 5,
onDestinationSelected: horizontal: 12,
_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(),
), ),
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), : Obx(
width: 80, () => NavigationRail(
child: userAndSearchVertical(theme), groupAlignment: 0.5,
), selectedIndex:
VerticalDivider( _mainController.selectedIndex.value,
width: 1, onDestinationSelected: _mainController.setIndex,
endIndent: padding.bottom, labelType: .selected,
color: theme.colorScheme.outline.withValues(alpha: 0.06), leading: userAndSearchVertical(theme),
), destinations: _mainController.navigationBars
], .map(
Expanded( (e) => NavigationRailDestination(
child: _mainController.mainTabBarView label: Text(e.label),
? CustomTabBarView( icon: _buildIcon(type: e),
scrollDirection: useBottomNav selectedIcon: _buildIcon(
? Axis.horizontal type: e,
: Axis.vertical, selected: true,
physics: const NeverScrollableScrollPhysics(), ),
controller: _mainController.controller, ),
children: _mainController.navigationBars )
.map((i) => i.page) .toList(),
.toList(), ),
) )
: PageView( : Container(
physics: const NeverScrollableScrollPhysics(), width: 80,
controller: _mainController.controller, padding: const .only(top: 10),
children: _mainController.navigationBars child: userAndSearchVertical(theme),
.map((i) => i.page) ),
.toList(), 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<MainApp>
return bottomNav; return bottomNav;
} }
Widget _buildIcon({ Widget _buildIcon({required NavigationBarType type, bool selected = false}) {
required NavigationBarType type,
bool selected = false,
}) {
final icon = selected ? type.selectIcon : type.icon; final icon = selected ? type.selectIcon : type.icon;
return type == NavigationBarType.dynamics return type == .dynamics
? Obx( ? Obx(
() { () {
final dynCount = _mainController.dynCount.value; final dynCount = _mainController.dynCount.value;
return Badge( return Badge(
isLabelVisible: dynCount > 0, isLabelVisible: dynCount > 0,
label: label: _mainController.dynamicBadgeMode == .number
_mainController.dynamicBadgeMode == DynamicBadgeMode.number
? Text(dynCount.toString()) ? Text(dynCount.toString())
: null, : null,
padding: const EdgeInsets.symmetric(horizontal: 6), padding: const .symmetric(horizontal: 6),
child: icon, child: icon,
); );
}, },
@@ -487,69 +475,9 @@ class _MainAppState extends State<MainApp>
Widget userAndSearchVertical(ThemeData theme) { Widget userAndSearchVertical(ThemeData theme) {
return Column( return Column(
children: [ children: [
Semantics( userAvatar(theme: theme, mainController: _mainController),
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,
),
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Obx( msgBadge(_mainController),
() => _mainController.accountService.isLogin.value
? msgBadge(_mainController)
: const SizedBox.shrink(),
),
IconButton( IconButton(
tooltip: '搜索', tooltip: '搜索',
icon: const Icon( icon: const Icon(

View File

@@ -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/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/http/loading_state.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/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/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/home/view.dart'; import 'package:PiliPlus/pages/home/view.dart';
@@ -70,16 +68,17 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
return onBuild( return onBuild(
Column( Column(
children: [ children: [
const SizedBox(height: 10), Padding(
_buildHeaderActions, padding: const .symmetric(vertical: 10),
const SizedBox(height: 10), child: _buildHeaderActions,
),
Expanded( Expanded(
child: Material( child: Material(
type: MaterialType.transparency, type: .transparency,
child: refreshIndicator( child: refreshIndicator(
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: ListView( child: ListView(
padding: const EdgeInsets.only(bottom: 100), padding: const .only(bottom: 100),
controller: controller.scrollController, controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
children: [ children: [
@@ -102,7 +101,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
Widget _buildActions(Color primary) { Widget _buildActions(Color primary) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: .spaceEvenly,
children: controller.list children: controller.list
.map( .map(
(e) => Flexible( (e) => Flexible(
@@ -115,14 +114,10 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
aspectRatio: 1, aspectRatio: 1,
child: Column( child: Column(
spacing: 6, spacing: 6,
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: .center,
children: [ children: [
Icon( Icon(size: e.size, e.icon, color: primary),
size: e.size,
e.icon,
color: primary,
),
Text( Text(
e.title, e.title,
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
@@ -139,9 +134,12 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
} }
Widget get _buildHeaderActions { Widget get _buildHeaderActions {
const iconSize = 22.0;
const padding = EdgeInsets.all(8);
const style = ButtonStyle(tapTargetSize: .shrinkWrap);
return Row( return Row(
spacing: 5, spacing: 5,
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: .end,
children: [ children: [
if (widget.showBackBtn) if (widget.showBackBtn)
const Expanded( const Expanded(
@@ -155,11 +153,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
), ),
if (!_mainController.hasHome) ...[ if (!_mainController.hasHome) ...[
IconButton( IconButton(
iconSize: 22, iconSize: iconSize,
padding: const EdgeInsets.all(8), padding: padding,
style: const ButtonStyle( style: style,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '搜索', tooltip: '搜索',
onPressed: () => Get.toNamed('/search'), onPressed: () => Get.toNamed('/search'),
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
@@ -170,11 +166,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
() { () {
final anonymity = MineController.anonymity.value; final anonymity = MineController.anonymity.value;
return IconButton( return IconButton(
iconSize: 22, iconSize: iconSize,
padding: const EdgeInsets.all(8), padding: padding,
style: const ButtonStyle( style: style,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: "${anonymity ? '退出' : '进入'}无痕模式", tooltip: "${anonymity ? '退出' : '进入'}无痕模式",
onPressed: MineController.onChangeAnonymity, onPressed: MineController.onChangeAnonymity,
icon: anonymity icon: anonymity
@@ -184,11 +178,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
}, },
), ),
IconButton( IconButton(
iconSize: 22, iconSize: iconSize,
padding: const EdgeInsets.all(8), padding: padding,
style: const ButtonStyle( style: style,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '设置账号模式', tooltip: '设置账号模式',
onPressed: () => LoginPageController.switchAccountDialog(context), onPressed: () => LoginPageController.switchAccountDialog(context),
icon: const Icon(Icons.switch_account_outlined), icon: const Icon(Icons.switch_account_outlined),
@@ -196,11 +188,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
Obx( Obx(
() { () {
return IconButton( return IconButton(
iconSize: 22, iconSize: iconSize,
padding: const EdgeInsets.all(8), padding: padding,
style: const ButtonStyle( style: style,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '切换至${controller.nextThemeType.desc}主题', tooltip: '切换至${controller.nextThemeType.desc}主题',
onPressed: controller.onChangeTheme, onPressed: controller.onChangeTheme,
icon: controller.themeType.value.icon, icon: controller.themeType.value.icon,
@@ -208,11 +198,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
}, },
), ),
IconButton( IconButton(
iconSize: 22, iconSize: iconSize,
padding: const EdgeInsets.all(8), padding: padding,
style: const ButtonStyle( style: style,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '设置', tooltip: '设置',
onPressed: () => Get.toNamed('/setting', preventDuplicates: false), onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
icon: const Icon(Icons.settings_outlined), icon: const Icon(Icons.settings_outlined),
@@ -240,8 +228,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
color: secondary, color: secondary,
); );
return Obx(() { return Obx(() {
final UserInfoData userInfo = controller.userInfo.value; final userInfo = controller.userInfo.value;
final LevelInfo? levelInfo = userInfo.levelInfo; final levelInfo = userInfo.levelInfo;
final hasLevel = levelInfo != null; final hasLevel = levelInfo != null;
final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0; final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0;
final userStat = controller.userStat.value; final userStat = controller.userStat.value;
@@ -249,7 +237,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: .opaque,
onTap: controller.onLogin, onTap: controller.onLogin,
onLongPress: () { onLongPress: () {
Feedback.forLongPress(context); Feedback.forLongPress(context);
@@ -259,16 +247,16 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
? null ? null
: () => controller.onLogin(true), : () => controller.onLogin(true),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
children: [ children: [
const SizedBox(width: 20), const SizedBox(width: 20),
userInfo.face != null userInfo.face != null
? Stack( ? Stack(
clipBehavior: Clip.none, clipBehavior: .none,
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: userInfo.face, src: userInfo.face,
type: ImageType.avatar, type: .avatar,
width: 55, width: 55,
height: 55, height: 55,
), ),
@@ -297,9 +285,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: .min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: .center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Row( Row(
spacing: 6, spacing: 6,
@@ -313,6 +301,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
? theme.colorScheme.vipColor ? theme.colorScheme.vipColor
: null, : null,
), ),
maxLines: 1,
overflow: .ellipsis,
), ),
), ),
Image.asset( Image.asset(
@@ -376,7 +366,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: .spaceEvenly,
children: [ children: [
_btn( _btn(
count: userStat.dynamicCount, count: userStat.dynamicCount,
@@ -422,14 +412,14 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, spacing: 4,
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: .min,
mainAxisAlignment: .center,
children: [ children: [
Text( Text(
count?.toString() ?? '-', count?.toString() ?? '-',
style: countStyle, style: countStyle,
), ),
const SizedBox(height: 4),
Text( Text(
name, name,
style: labelStyle, style: labelStyle,
@@ -466,7 +456,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
text: '我的收藏 ', text: '我的收藏 ',
style: TextStyle( style: TextStyle(
fontSize: theme.textTheme.titleMedium!.fontSize, fontSize: theme.textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold, fontWeight: .bold,
), ),
), ),
if (controller.favFolderCount != null) if (controller.favFolderCount != null)
@@ -516,19 +506,17 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
return SizedBox( return SizedBox(
height: 200, height: 200,
child: ListView.separated( 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), itemCount: response.list.length + (flag ? 1 : 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (flag && index == favFolderList.length) { if (flag && index == favFolderList.length) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 35), padding: const .only(bottom: 35),
child: Center( child: Center(
child: IconButton( child: IconButton(
tooltip: '查看更多', tooltip: '查看更多',
style: ButtonStyle( style: ButtonStyle(
padding: const WidgetStatePropertyAll( padding: const WidgetStatePropertyAll(.zero),
EdgeInsets.zero,
),
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.secondaryContainer.withValues( theme.colorScheme.secondaryContainer.withValues(
alpha: 0.5, alpha: 0.5,
@@ -560,8 +548,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
); );
} }
}, },
scrollDirection: Axis.horizontal, scrollDirection: .horizontal,
separatorBuilder: (context, index) => const SizedBox(width: 14), separatorBuilder: (_, _) => const SizedBox(width: 14),
), ),
); );
}, },
@@ -571,7 +559,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
child: Center( child: Center(
child: Text( child: Text(
errMsg ?? '', errMsg ?? '',
textAlign: TextAlign.center, textAlign: .center,
), ),
), ),
), ),

View File

@@ -42,7 +42,7 @@ class FavFolderItem extends StatelessWidget {
color: theme.colorScheme.onInverseSurface.withValues( color: theme.colorScheme.onInverseSurface.withValues(
alpha: 0.4, alpha: 0.4,
), ),
offset: const Offset(4, -12), offset: const Offset(6, -8),
blurRadius: 0.0, blurRadius: 0.0,
spreadRadius: 0.0, spreadRadius: 0.0,
), ),

View File

@@ -31,8 +31,8 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
super.build(context); super.build(context);
return onBuild( return onBuild(
Container( Container(
clipBehavior: Clip.hardEdge, clipBehavior: .hardEdge,
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), margin: const .symmetric(horizontal: StyleString.safeSpace),
decoration: const BoxDecoration(borderRadius: StyleString.mdRadius), decoration: const BoxDecoration(borderRadius: StyleString.mdRadius),
child: refreshIndicator( child: refreshIndicator(
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
@@ -41,10 +41,7 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverPadding( SliverPadding(
padding: const EdgeInsets.only( padding: const .only(top: StyleString.cardSpace, bottom: 100),
top: StyleString.cardSpace,
bottom: 100,
),
sliver: Obx(() => _buildBody(controller.loadingState.value)), sliver: Obx(() => _buildBody(controller.loadingState.value)),
), ),
], ],
@@ -87,7 +84,7 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
), ),
child: Text( child: Text(
'上次看到这里\n点击刷新', '上次看到这里\n点击刷新',
textAlign: TextAlign.center, textAlign: .center,
style: TextStyle( style: TextStyle(
color: Theme.of( color: Theme.of(
context, context,

View File

@@ -44,7 +44,7 @@ class _SettingPageState extends State<SettingPage> {
final RxBool _noAccount = Accounts.account.isEmpty.obs; final RxBool _noAccount = Accounts.account.isEmpty.obs;
late bool _isPortrait; late bool _isPortrait;
final List<_SettingsModel> _items = const [ static const List<_SettingsModel> _items = [
_SettingsModel( _SettingsModel(
type: SettingType.privacySetting, type: SettingType.privacySetting,
subtitle: '黑名单、无痕模式', subtitle: '黑名单、无痕模式',

View File

@@ -91,6 +91,17 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
void initState() { void initState() {
_cdnSpeedTest = Pref.cdnSpeedTest; _cdnSpeedTest = Pref.cdnSpeedTest;
if (_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; final length = CDNService.values.length;
_cdnResList = List.generate( _cdnResList = List.generate(
length, length,
@@ -156,11 +167,7 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
} }
} }
late final _dio = Dio() late final Dio _dio;
..options.headers = {
'user-agent': UaType.pc.ua,
'referer': HttpString.baseUrl,
};
Future<void> _measureDownloadSpeed(String url, int index) async { Future<void> _measureDownloadSpeed(String url, int index) async {
const maxSize = 8 * 1024 * 1024; const maxSize = 8 * 1024 * 1024;

View File

@@ -1456,7 +1456,7 @@ class VideoDetailController extends GetxController
} }
RxList<Subtitle> subtitles = RxList<Subtitle>(); RxList<Subtitle> subtitles = RxList<Subtitle>();
late final Map<int, String> vttSubtitles = {}; final Map<int, ({bool isData, String id})> vttSubtitles = {};
late final RxInt vttSubtitlesIndex = (-1).obs; late final RxInt vttSubtitlesIndex = (-1).obs;
late final RxBool showVP = true.obs; late final RxBool showVP = true.obs;
late final RxList<ViewPointSegment> viewPointList = <ViewPointSegment>[].obs; late final RxList<ViewPointSegment> viewPointList = <ViewPointSegment>[].obs;
@@ -1471,19 +1471,21 @@ class VideoDetailController extends GetxController
return; return;
} }
Future<void> setSub(String subtitle) async { Future<void> setSub(({bool isData, String id}) subtitle) async {
final sub = subtitles[index - 1]; final sub = subtitles[index - 1];
await plPlayerController.videoPlayerController?.setSubtitleTrack( await plPlayerController.videoPlayerController?.setSubtitleTrack(
SubtitleTrack.data( SubtitleTrack(
subtitle, subtitle.id,
title: sub.lanDoc, sub.lanDoc,
language: sub.lan, sub.lan,
uri: !subtitle.isData,
data: subtitle.isData,
), ),
); );
vttSubtitlesIndex.value = index; vttSubtitlesIndex.value = index;
} }
String? subtitle = vttSubtitles[index - 1]; ({bool isData, String id})? subtitle = vttSubtitles[index - 1];
if (subtitle != null) { if (subtitle != null) {
await setSub(subtitle); await setSub(subtitle);
} else { } else {
@@ -1491,8 +1493,9 @@ class VideoDetailController extends GetxController
subtitles[index - 1].subtitleUrl!, subtitles[index - 1].subtitleUrl!,
); );
if (!isClosed && result != null) { if (!isClosed && result != null) {
vttSubtitles[index - 1] = result; final subtitle = (isData: true, id: result);
await setSub(result); vttSubtitles[index - 1] = subtitle;
await setSub(subtitle);
} }
} }
} }
@@ -1665,6 +1668,8 @@ class VideoDetailController extends GetxController
?..removeListener(scrollListener) ?..removeListener(scrollListener)
..dispose(); ..dispose();
animController?.dispose(); animController?.dispose();
subtitles.clear();
vttSubtitles.clear();
super.onClose(); super.onClose();
} }

View File

@@ -324,7 +324,6 @@ class _DownloadPanelState extends State<DownloadPanel> {
required ugc.BaseEpisodeItem episode, required ugc.BaseEpisodeItem episode,
}) { }) {
late String title; late String title;
String? cover;
num? duration; num? duration;
int? pubdate; int? pubdate;
int? view; int? view;
@@ -332,6 +331,11 @@ class _DownloadPanelState extends State<DownloadPanel> {
bool? isCharging; bool? isCharging;
int? cid; int? cid;
String? cover;
int? width;
int? height;
bool cacheWidth = false;
switch (episode) { switch (episode) {
case Part part: case Part part:
cid = part.cid; cid = part.cid;
@@ -339,15 +343,27 @@ class _DownloadPanelState extends State<DownloadPanel> {
title = part.part ?? widget.videoDetail!.title!; title = part.part ?? widget.videoDetail!.title!;
duration = part.duration; duration = part.duration;
pubdate = part.ctime; pubdate = part.ctime;
if (part.dimension case final dimension?) {
width = dimension.width;
height = dimension.height;
}
break; break;
case ugc.EpisodeItem item: case ugc.EpisodeItem item:
cid = item.cid; cid = item.cid;
title = item.title!; title = item.title!;
cover = item.arc?.pic; if (item.arc case final arc?) {
duration = item.arc?.duration; cover = arc.pic;
pubdate = item.arc?.pubdate; duration = arc.duration;
view = item.arc?.stat?.view; pubdate = arc.pubdate;
danmaku = item.arc?.stat?.danmaku; 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) { if (item.attribute == 8) {
isCharging = true; isCharging = true;
} }
@@ -363,8 +379,15 @@ class _DownloadPanelState extends State<DownloadPanel> {
duration = item.duration == null ? null : item.duration! ~/ 1000; duration = item.duration == null ? null : item.duration! ~/ 1000;
} }
pubdate = item.pubTime; pubdate = item.pubTime;
if (item.dimension case final dimension?) {
width = dimension.width;
height = dimension.height;
}
break; break;
} }
if (width != null && height != null) {
cacheWidth = width <= height;
}
late final primary = theme.colorScheme.primary; late final primary = theme.colorScheme.primary;
return Padding( return Padding(
@@ -401,6 +424,7 @@ class _DownloadPanelState extends State<DownloadPanel> {
src: cover, src: cover,
width: 140.8, width: 140.8,
height: 88, height: 88,
cacheWidth: cacheWidth,
), ),
if (duration != null && duration > 0) if (duration != null && duration > 0)
PBadge( PBadge(

View File

@@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert' show jsonDecode, utf8;
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
@@ -717,34 +717,41 @@ class HeaderControlState extends State<HeaderControl>
final first = file.files.first; final first = file.files.first;
final path = first.path; final path = first.path;
if (path != null) { 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 name = first.name;
final length = videoDetailCtr.subtitles.length;
if (name.endsWith('.json')) { 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<List, String>( sub = await compute<List, String>(
VideoHttp.processList, VideoHttp.processList,
jsonDecode(sub)['body'], jsonDecode(sub)['body'],
); );
if (!mounted) return; 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(
videoDetailCtr Subtitle(
..subtitles.add( lan: '',
Subtitle( lanDoc: name.split('.').firstOrNull ?? name,
lan: '', ),
lanDoc: name.split('.').firstOrNull ?? name, );
),
)
..vttSubtitles[length] = sub;
await videoDetailCtr.setSubtitle(length + 1); await videoDetailCtr.setSubtitle(length + 1);
} }
} }