Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-04-29 12:47:14 +08:00
parent e185ed1a85
commit e0ae93847b
104 changed files with 480 additions and 389 deletions

View File

@@ -1,4 +1,4 @@
import 'dart:convert';
import 'dart:convert' show utf8;
import 'package:PiliPlus/common/constants.dart';
import 'package:crypto/crypto.dart';

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:async' show FutureOr;
import 'dart:io' show Platform, Directory, File;
import 'package:flutter/services.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:path/path.dart' as path;
abstract final class AssetUtils {

View File

@@ -1,4 +1,4 @@
abstract final class FavUtils {
abstract final class BiliUtils {
static bool isDefaultFav(int? attr) {
if (attr == null) {
return false;
@@ -16,4 +16,13 @@ abstract final class FavUtils {
static bool isPublicFav(int attr) {
return (attr & 1) == 0;
}
static bool isCustomFollowTag(int? tagid) {
return tagid != null && tagid != 0 && tagid != -10 && tagid != -2;
}
static String levelName(
Object level, {
bool isSeniorMember = false,
}) => 'assets/images/lv/lv${isSeniorMember ? '6_s' : level}.png';
}

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:io' show Directory, File;
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';

View File

@@ -1,6 +1,6 @@
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show Offset, Size;
import 'package:screen_retriever/screen_retriever.dart';
Future<Offset> calcWindowPosition(Size windowSize) async {

View File

@@ -0,0 +1,17 @@
import 'package:flutter/rendering.dart' show Color;
abstract final class ColourUtils {
static Color parseColor(String color) =>
Color(int.parse('FF${color.substring(1)}', radix: 16));
static Color parseMedalColor(String color) => Color(
int.parse('${color.substring(7)}${color.substring(1, 7)}', radix: 16),
);
static Color index2Color(int index, Color color) => switch (index) {
0 => const Color(0xFFfdad13),
1 => const Color(0xFF8aace1),
2 => const Color(0xFFdfa777),
_ => color,
};
}

View File

@@ -0,0 +1,15 @@
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
abstract final class ConnectivityUtils {
static Future<bool> get isWiFi async {
try {
return PlatformUtils.isMobile &&
(await Connectivity().checkConnectivity()).contains(
ConnectivityResult.wifi,
);
} catch (_) {
return true;
}
}
}

View File

@@ -0,0 +1,21 @@
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/widgets.dart' show WidgetsBinding, Size;
abstract final class DeviceUtils {
static late int sdkInt;
static bool get isTablet {
return size.shortestSide >= 600;
}
static Size get size {
final view = WidgetsBinding.instance.platformDispatcher.views.first;
return view.physicalSize / view.devicePixelRatio;
}
static String get platformName => PlatformUtils.isDesktop
? 'desktop'
: isTablet
? 'pad'
: 'phone';
}

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart';
/// from Getx
@@ -72,10 +71,4 @@ extension ContextExtensions on BuildContext {
/// True if the current device is Tablet
bool get isTablet => isSmallTablet || isLargeTablet;
String get platformName => PlatformUtils.isDesktop
? 'desktop'
: isTablet
? 'pad'
: 'phone';
}

View File

@@ -3,3 +3,16 @@ import 'package:PiliPlus/grpc/bilibili/app/archive/v1.pb.dart' show Dimension;
extension DimensionExt on Dimension {
bool get isVertical => rotate == .ONE ? width > height : height > width;
}
extension StringExt on String {
bool get isVerticalFromUri {
try {
final params = Uri.parse(this).queryParameters;
final width = int.parse(params['player_width']!);
final height = int.parse(params['player_height']!);
return params['player_rotate'] == '1' ? width > height : height > width;
} catch (_) {
return false;
}
}
}

View File

@@ -1,4 +1,4 @@
import 'dart:io';
import 'dart:io' show FileSystemEntity, Directory;
extension FileSystemEntityExt on FileSystemEntity {
Future<void> tryDel({bool recursive = false}) async {

View File

@@ -6,10 +6,10 @@ extension GetExt on GetInterface {
GetInstance().putOrFind(dep, tag: tag);
void updateMyAppTheme() {
final (l, d) = MyApp.getAllTheme();
final (light, dark) = MyApp.getAllTheme();
rootController
..theme = l
..darkTheme = d
..theme = light
..darkTheme = dark
..update();
}
}

View File

@@ -1,6 +1,6 @@
import 'dart:math' show pow;
import 'package:flutter/widgets.dart';
import 'package:flutter/widgets.dart' show BuildContext, MediaQuery;
extension ImageExtension on num {
int? cacheSize(BuildContext context) {

View File

@@ -1,4 +1,4 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/widgets.dart' show ScrollController, Curves;
extension ScrollControllerExt on ScrollController {
void animToTop() => animTo(0);

View File

@@ -1,9 +1,16 @@
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart'
show ThemeData, Color, ColorScheme, Brightness, Colors;
const _pinkLight = Color(0xFFFF6699);
const _pinkDark = Color(0xFFD44E7D);
extension ThemeDataExt on ThemeData {
bool get isLight => brightness.isLight;
bool get isDark => brightness.isDark;
}
extension ColorSchemeExt on ColorScheme {
Color get vipColor => brightness.isLight ? _pinkLight : _pinkDark;

View File

@@ -1,6 +1,6 @@
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
import 'dart:convert';
import 'dart:convert' show ascii, base64;
import 'package:PiliPlus/utils/utils.dart';
import 'package:uuid/v4.dart';

View File

@@ -1,15 +1,17 @@
import 'dart:io';
import 'dart:io' show File, Platform;
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:typed_data' show Uint8List;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/utils/device_utils.dart';
import 'package:PiliPlus/utils/extension/file_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/path_utils.dart';
import 'package:PiliPlus/utils/permission_handler.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/share_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:dio/dio.dart';
@@ -40,7 +42,7 @@ abstract final class ImageUtils {
.share(
ShareParams(
files: [XFile(path)],
sharePositionOrigin: await Utils.sharePositionOrigin,
sharePositionOrigin: await ShareUtils.sharePositionOrigin,
),
)
.whenComplete(File(path).tryDel);
@@ -80,7 +82,7 @@ abstract final class ImageUtils {
static Future<bool> checkPermissionDependOnSdkInt() {
if (Platform.isAndroid) {
if (Utils.sdkInt < 29) {
if (DeviceUtils.sdkInt < 29) {
return requestPer();
} else {
return Future.syncValue(true);

6
lib/utils/parse_int.dart Normal file
View File

@@ -0,0 +1,6 @@
int? safeToInt(dynamic value) => switch (value) {
int() => value,
String() => int.tryParse(value),
num() => value.toInt(),
_ => null,
};

View File

@@ -1,4 +1,4 @@
String? noneNullOrEmptyString(String? value) {
String? nonNullOrEmptyString(String? value) {
if (value == null || value.isEmpty) return null;
return value;
}

View File

@@ -9,7 +9,9 @@ import 'package:PiliPlus/models/common/reply/reply_sort_type.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
@@ -98,6 +100,7 @@ abstract final class ReplyUtils {
await Future.delayed(const Duration(seconds: 8));
}
void showReplyCheckResult(String message, {bool isBan = false}) {
final theme = ThemeUtils.theme;
final actions = [
if (isBan)
TextButton(
@@ -117,7 +120,7 @@ abstract final class ReplyUtils {
'/webview',
parameters: {
'url':
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
'https://www.bilibili.com/h5/comment/appeal?${ThemeUtils.themeUrl(theme.isDark)}',
},
);
},
@@ -128,7 +131,7 @@ abstract final class ReplyUtils {
onPressed: Get.back,
child: Text(
'关闭',
style: TextStyle(color: Get.theme.colorScheme.outline),
style: TextStyle(color: theme.colorScheme.outline),
),
),
];

View File

@@ -30,11 +30,13 @@ import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/extension/size_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
@@ -344,6 +346,7 @@ abstract final class RequestUtils {
clearCookie: true,
);
final isSuccess = res.isSuccess;
final theme = ThemeUtils.theme;
final actions = [
if (!isSuccess)
TextButton(
@@ -354,7 +357,7 @@ abstract final class RequestUtils {
'/webview',
parameters: {
'url':
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
'https://www.bilibili.com/h5/comment/appeal?${ThemeUtils.themeUrl(theme.isDark)}',
},
);
},
@@ -365,7 +368,7 @@ abstract final class RequestUtils {
onPressed: Get.back,
child: Text(
'关闭',
style: TextStyle(color: Get.theme.colorScheme.outline),
style: TextStyle(color: theme.colorScheme.outline),
),
),
];

View File

@@ -0,0 +1,41 @@
import 'dart:io' show Platform;
import 'package:PiliPlus/utils/device_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/rendering.dart' show Rect;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:share_plus/share_plus.dart';
abstract final class ShareUtils {
static bool? _isIpad;
static Future<bool> get isIpad async {
if (!Platform.isIOS) return false;
return _isIpad ??= (await DeviceInfoPlugin().iosInfo).model
.toLowerCase()
.contains('ipad');
}
static Future<Rect?> get sharePositionOrigin async {
if (await isIpad) {
final screenSize = DeviceUtils.size;
return Rect.fromLTRB(0, 0, screenSize.width, screenSize.height / 2);
}
return null;
}
static Future<void> shareText(String text) async {
if (PlatformUtils.isDesktop) {
Utils.copyText(text);
return;
}
try {
await SharePlus.instance.share(
ShareParams(text: text, sharePositionOrigin: await sharePositionOrigin),
);
} catch (e) {
SmartDialog.showToast(e.toString());
}
}
}

View File

@@ -30,6 +30,7 @@ import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPlus/utils/device_utils.dart';
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/login_utils.dart';
@@ -192,7 +193,9 @@ abstract final class Pref {
static FullScreenMode get fullScreenMode {
int? index = _setting.get(SettingBoxKey.fullScreenMode);
if (index == null) {
final FullScreenMode mode = horizontalScreen && isTablet ? .none : .auto;
final FullScreenMode mode = horizontalScreen && DeviceUtils.isTablet
? .none
: .auto;
_setting.put(SettingBoxKey.fullScreenMode, mode.index);
return mode;
}
@@ -594,15 +597,14 @@ abstract final class Pref {
static bool get optTabletNav =>
_setting.get(SettingBoxKey.optTabletNav, defaultValue: true);
static bool get horizontalScreen =>
_setting.get(SettingBoxKey.horizontalScreen) ?? isTablet;
static bool get isTablet {
final view = WidgetsBinding.instance.platformDispatcher.views.first;
final size = view.physicalSize / view.devicePixelRatio;
final isTablet = size.shortestSide >= 600;
_setting.put(SettingBoxKey.horizontalScreen, isTablet);
return isTablet;
static bool get horizontalScreen {
bool? horizontalScreen = _setting.get(SettingBoxKey.horizontalScreen);
if (horizontalScreen == null) {
final isTablet = DeviceUtils.isTablet;
_setting.put(SettingBoxKey.horizontalScreen, isTablet);
return isTablet;
}
return horizontalScreen;
}
static String get banWordForDyn =>

View File

@@ -0,0 +1,34 @@
import 'dart:io' show File;
import 'dart:typed_data' show Uint8List;
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
abstract final class StorageUtils {
static Future<void> saveBytes2File({
required String name,
required Uint8List bytes,
required List<String> allowedExtensions,
FileType type = FileType.custom,
}) async {
try {
final path = await FilePicker.saveFile(
allowedExtensions: allowedExtensions,
type: type,
fileName: name,
bytes: PlatformUtils.isDesktop ? null : bytes,
);
if (path == null) {
SmartDialog.showToast("取消保存");
return;
}
if (PlatformUtils.isDesktop) {
await File(path).writeAsBytes(bytes);
}
SmartDialog.showToast("已保存");
} catch (e) {
SmartDialog.showToast("保存失败: $e");
}
}
}

View File

@@ -1,11 +1,31 @@
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/main.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/cupertino.dart' show CupertinoThemeData;
import 'package:flutter/foundation.dart' show PlatformDispatcher;
import 'package:flutter/material.dart';
abstract final class ThemeUtils {
static late ThemeData lightTheme;
static late ThemeData darkTheme;
static late ThemeMode themeMode;
static ThemeData get theme {
if (themeMode == .dark ||
(themeMode == .system &&
PlatformDispatcher.instance.platformBrightness == .dark)) {
return darkTheme;
}
return lightTheme;
}
static bool get isDarkMode => theme.isDark;
static String themeUrl(bool isDark) =>
'native.theme=${isDark ? 2 : 1}&night=${isDark ? 1 : 0}';
static ThemeData getThemeData({
required ColorScheme colorScheme,
required bool isDynamic,
@@ -134,9 +154,6 @@ abstract final class ThemeUtils {
if (Pref.isPureBlackTheme) {
themeData = darkenTheme(themeData);
}
if (Pref.darkVideoPage) {
MyApp.darkThemeData = themeData;
}
}
return themeData;
}

View File

@@ -4,8 +4,7 @@ import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kDebugMode, debugPrint;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
abstract final class UrlUtils {

View File

@@ -1,18 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:convert' show JsonEncoder, base64;
import 'dart:math' show Random;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:catcher_2/catcher_2.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart';
import 'package:flutter/services.dart'
show Clipboard, ClipboardData, MethodChannel;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:share_plus/share_plus.dart';
abstract final class Utils {
static final random = Random();
@@ -21,119 +14,6 @@ abstract final class Utils {
static const jsonEncoder = JsonEncoder.withIndent(' ');
static bool isCustomFollowTag(int? tagid) {
return tagid != null && tagid != 0 && tagid != -10 && tagid != -2;
}
static String levelName(
Object level, {
bool isSeniorMember = false,
}) => 'assets/images/lv/lv${isSeniorMember ? '6_s' : level}.png';
static Color index2Color(int index, Color color) => switch (index) {
0 => const Color(0xFFfdad13),
1 => const Color(0xFF8aace1),
2 => const Color(0xFFdfa777),
_ => color,
};
static bool getDimensionFromUri(String uri) {
try {
final params = Uri.parse(uri).queryParameters;
final width = int.parse(params['player_width']!);
final height = int.parse(params['player_height']!);
return params['player_rotate'] == '1' ? width > height : height > width;
} catch (_) {
return false;
}
}
static String themeUrl(bool isDark) =>
'native.theme=${isDark ? 2 : 1}&night=${isDark ? 1 : 0}';
static Future<void> saveBytes2File({
required String name,
required Uint8List bytes,
required List<String> allowedExtensions,
FileType type = FileType.custom,
}) async {
try {
final path = await FilePicker.saveFile(
allowedExtensions: allowedExtensions,
type: type,
fileName: name,
bytes: PlatformUtils.isDesktop ? null : bytes,
);
if (path == null) {
SmartDialog.showToast("取消保存");
return;
}
if (PlatformUtils.isDesktop) {
await File(path).writeAsBytes(bytes);
}
SmartDialog.showToast("已保存");
} catch (e) {
SmartDialog.showToast("保存失败: $e");
}
}
static int? safeToInt(dynamic value) => switch (value) {
int e => e,
String e => int.tryParse(e),
num e => e.toInt(),
_ => null,
};
static Future<bool> get isWiFi async {
try {
return PlatformUtils.isMobile &&
(await Connectivity().checkConnectivity()).contains(
ConnectivityResult.wifi,
);
} catch (_) {
return true;
}
}
static Color parseColor(String color) =>
Color(int.parse('FF${color.substring(1)}', radix: 16));
static Color parseMedalColor(String color) => Color(
int.parse('${color.substring(7)}${color.substring(1, 7)}', radix: 16),
);
static late int sdkInt;
static bool? _isIpad;
static Future<bool> get isIpad async {
if (!Platform.isIOS) return false;
return _isIpad ??= (await DeviceInfoPlugin().iosInfo).model
.toLowerCase()
.contains('ipad');
}
static Future<Rect?> get sharePositionOrigin async {
if (await isIpad) {
final size = Get.size;
return Rect.fromLTRB(0, 0, size.width, size.height / 2);
}
return null;
}
static Future<void> shareText(String text) async {
if (PlatformUtils.isDesktop) {
copyText(text);
return;
}
try {
await SharePlus.instance.share(
ShareParams(text: text, sharePositionOrigin: await sharePositionOrigin),
);
} catch (e) {
SmartDialog.showToast(e.toString());
}
}
static final numericRegex = RegExp(r'^[\d\.]+$');
static bool isStringNumeric(String str) {
return numericRegex.hasMatch(str);

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/models/common/video/cdn_type.dart';
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' show kDebugMode, debugPrint;
abstract final class VideoUtils {
static CDNService cdnService = Pref.defaultCDNService;