mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-27 21:00:17 +08:00
tweaks (#2426)
* opt: danmaku weight
* opt: cache clean
* opt: level img
* opt: play icon
* opt: svg big-vip
* opt: webview ua
* opt: simple dialog
* feat: export vtt
* tweak
* opt: mapIndexed
* feat: more subtitle
* refa: settings page
* feat: codec list options
* drawPath
Signed-off-by: dom <githubaccount56556@proton.me>
* custom dialog option
Signed-off-by: dom <githubaccount56556@proton.me>
* update
Signed-off-by: dom <githubaccount56556@proton.me>
* Revert "drawPath"
This reverts commit e8a4b19f0f.
* opt: _initStreamIndex
* fix: avoid gap
* fix: scale [skip ci]
* fix: hide repost menu not login
* tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
---------
Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
committed by
GitHub
parent
3dee6a85e5
commit
9d94c72e95
@@ -1,3 +1,6 @@
|
||||
import 'package:PiliPlus/common/widgets/svg/level_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract final class BiliUtils {
|
||||
static bool isDefaultFav(int? attr) {
|
||||
if (attr == null) {
|
||||
@@ -21,8 +24,12 @@ abstract final class BiliUtils {
|
||||
return tagid != null && tagid != 0 && tagid != -10 && tagid != -2;
|
||||
}
|
||||
|
||||
static String levelName(
|
||||
Object level, {
|
||||
// https://s1.hdslb.com/bfs/svg-next/font/2025-10-27/freshspace-zpjpp3aqht.css
|
||||
static Widget levelPicture(
|
||||
int level, {
|
||||
bool isSeniorMember = false,
|
||||
}) => 'assets/images/lv/lv${isSeniorMember ? '6_s' : level}.png';
|
||||
double height = 11,
|
||||
}) {
|
||||
return UserLevel(level, height: height, flash: isSeniorMember);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io' show Directory, File;
|
||||
|
||||
import 'package:PiliPlus/utils/extension/file_ext.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:cached_network_image_ce/cached_network_image.dart';
|
||||
@@ -10,18 +9,19 @@ import 'package:path_provider/path_provider.dart';
|
||||
abstract final class CacheManager {
|
||||
static late final DefaultCacheManager manager;
|
||||
|
||||
static Future<void> ensureInitialized() =>
|
||||
DefaultCacheManager.init().then((i) => manager = i);
|
||||
static Future<void> ensureInitialized() => DefaultCacheManager.init(
|
||||
maxNrOfCacheLength: Pref.maxCacheSize.toInt(),
|
||||
).then((i) => manager = i);
|
||||
|
||||
// 获取缓存目录
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
static Future<int> loadApplicationCache() async {
|
||||
try {
|
||||
final Directory tempDirectory = await getTemporaryDirectory();
|
||||
if (PlatformUtils.isDesktop) {
|
||||
return manager.getTotalLength();
|
||||
}
|
||||
|
||||
final Directory tempDirectory = await getTemporaryDirectory();
|
||||
if (tempDirectory.existsSync()) {
|
||||
return await getTotalSizeOfFilesInDir(tempDirectory);
|
||||
}
|
||||
@@ -81,22 +81,4 @@ abstract final class CacheManager {
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static Future<void> autoClearCache() async {
|
||||
// TODO: remove
|
||||
Directory(
|
||||
'${(await getTemporaryDirectory()).path}/libCachedImageData',
|
||||
).tryDel(recursive: true);
|
||||
if (Pref.autoClearCache) {
|
||||
await clearLibraryCache();
|
||||
} else {
|
||||
final maxCacheSize = Pref.maxCacheSize;
|
||||
if (maxCacheSize != 0) {
|
||||
final currCache = await loadApplicationCache();
|
||||
if (currCache >= maxCacheSize) {
|
||||
await clearLibraryCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import 'dart:convert' show ascii, base64;
|
||||
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:uuid/v4.dart';
|
||||
|
||||
abstract final class IdUtils {
|
||||
@@ -24,12 +25,6 @@ abstract final class IdUtils {
|
||||
static final avRegexExact = RegExp(r'^av(\d+)$', caseSensitive: false);
|
||||
static final digitOnlyRegExp = RegExp(r'^\d+$');
|
||||
|
||||
static void swap<T>(List<T> list, int idx1, int idx2) {
|
||||
final idx1Value = list[idx1];
|
||||
list[idx1] = list[idx2];
|
||||
list[idx2] = idx1Value;
|
||||
}
|
||||
|
||||
/// av转bv
|
||||
static String av2bv(int aid) {
|
||||
final bytes = ['B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0'];
|
||||
@@ -40,18 +35,18 @@ abstract final class IdUtils {
|
||||
tmp ~/= BASE;
|
||||
}
|
||||
|
||||
swap(bytes, 3, 9);
|
||||
swap(bytes, 4, 7);
|
||||
bytes
|
||||
..swap(3, 9)
|
||||
..swap(4, 7);
|
||||
|
||||
return bytes.join();
|
||||
}
|
||||
|
||||
/// bv转av
|
||||
static int bv2av(String bvid) {
|
||||
final bvidArr = bvid.codeUnits.sublist(3);
|
||||
|
||||
swap(bvidArr, 0, 6);
|
||||
swap(bvidArr, 1, 4);
|
||||
final bvidArr = bvid.codeUnits.sublist(3)
|
||||
..swap(0, 6)
|
||||
..swap(1, 4);
|
||||
|
||||
final tmp = bvidArr.fold(0, (pre, char) => pre * BASE + invData[char]!);
|
||||
return (tmp & MASK_CODE) ^ XOR_CODE;
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
|
||||
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
|
||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||
show ReplyInfo;
|
||||
@@ -169,99 +170,84 @@ abstract final class RequestUtils {
|
||||
String text = isSpecialFollowed ? '移除特别关注' : '加入特别关注';
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => SimpleDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final res = await MemberHttp.specialAction(
|
||||
fid: mid,
|
||||
isAdd: !isSpecialFollowed,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('$text成功');
|
||||
afterMod?.call(isSpecialFollowed ? 2 : -10);
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final result = await showModalBottomSheet<Set<int>>(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: min(640, context.mediaQueryShortestSide),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
final maxChildSize =
|
||||
PlatformUtils.isMobile &&
|
||||
!context.mediaQuerySize.isPortrait
|
||||
? 1.0
|
||||
: 0.7;
|
||||
return DraggableScrollableSheet(
|
||||
minChildSize: 0,
|
||||
maxChildSize: 1,
|
||||
snap: true,
|
||||
expand: false,
|
||||
snapSizes: [maxChildSize],
|
||||
initialChildSize: maxChildSize,
|
||||
builder: (context, scrollController) {
|
||||
return GroupPanel(
|
||||
mid: mid,
|
||||
tags: followStatus!.tag,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
followStatus!.tag = result.toList();
|
||||
afterMod?.call(result.contains(-10) ? -10 : 2);
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
'设置分组',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final res = await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: 2,
|
||||
reSrc: 11,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('取消关注成功');
|
||||
afterMod?.call(0);
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
'取消关注',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
children: [
|
||||
DialogOption(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
final res = await MemberHttp.specialAction(
|
||||
fid: mid,
|
||||
isAdd: !isSpecialFollowed,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('$text成功');
|
||||
afterMod?.call(isSpecialFollowed ? 2 : -10);
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
child: Text(text, style: const TextStyle(fontSize: 14)),
|
||||
),
|
||||
DialogOption(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
final result = await showModalBottomSheet<Set<int>>(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: min(640, context.mediaQueryShortestSide),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
final maxChildSize =
|
||||
PlatformUtils.isMobile &&
|
||||
!context.mediaQuerySize.isPortrait
|
||||
? 1.0
|
||||
: 0.7;
|
||||
return DraggableScrollableSheet(
|
||||
minChildSize: 0,
|
||||
maxChildSize: 1,
|
||||
snap: true,
|
||||
expand: false,
|
||||
snapSizes: [maxChildSize],
|
||||
initialChildSize: maxChildSize,
|
||||
builder: (context, scrollController) {
|
||||
return GroupPanel(
|
||||
mid: mid,
|
||||
tags: followStatus!.tag,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
followStatus!.tag = result.toList();
|
||||
afterMod?.call(result.contains(-10) ? -10 : 2);
|
||||
}
|
||||
},
|
||||
child: const Text('设置分组', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
DialogOption(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
final res = await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: 2,
|
||||
reSrc: 11,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
SmartDialog.showToast('取消关注成功');
|
||||
afterMod?.call(0);
|
||||
} else {
|
||||
res.toast();
|
||||
}
|
||||
},
|
||||
child: const Text('取消关注', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ abstract final class SettingBoxKey {
|
||||
defaultAudioQaCellular = 'defaultAudioQaCellular',
|
||||
autoPlayEnable = 'autoPlayEnable',
|
||||
fullScreenMode = 'fullScreenMode',
|
||||
defaultDecode = 'defaultDecode',
|
||||
secondDecode = 'secondDecode',
|
||||
preferCodecs = 'preferCodecs',
|
||||
defaultToastOp = 'defaultToastOp',
|
||||
defaultPicQa = 'defaultPicQa',
|
||||
enableHA = 'enableHA',
|
||||
@@ -56,7 +55,6 @@ abstract final class SettingBoxKey {
|
||||
banWordForRecommend = 'banWordForRecommend',
|
||||
applyFilterToRelatedVideos = 'applyFilterToRelatedVideos',
|
||||
autoUpdate = 'autoUpdate',
|
||||
autoClearCache = 'autoClearCache',
|
||||
maxCacheSize = 'maxCacheSize',
|
||||
defaultShowComment = 'defaultShowComment',
|
||||
replySortType = 'replySortType',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' show pow;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'
|
||||
show deviceTouchSlop;
|
||||
@@ -246,15 +245,33 @@ abstract final class Pref {
|
||||
defaultValue: AudioQuality.k192.code,
|
||||
);
|
||||
|
||||
static String get defaultDecode => _setting.get(
|
||||
SettingBoxKey.defaultDecode,
|
||||
defaultValue: VideoDecodeFormatType.AVC.codes.first,
|
||||
);
|
||||
static List<VideoDecodeFormatType> get preferCodecs {
|
||||
// TODO: remove next 2 version
|
||||
if (_setting.get('defaultDecode') case String codecStr) {
|
||||
String? codecStr2 = _setting.get('secondDecode');
|
||||
_setting.deleteAll(const ['defaultDecode', 'secondDecode']);
|
||||
final codecs = [
|
||||
VideoDecodeFormatType.values.firstWhere(
|
||||
(i) => i.codes.contains(codecStr),
|
||||
),
|
||||
if (codecStr2 != null && codecStr2 != codecStr)
|
||||
VideoDecodeFormatType.values.firstWhere(
|
||||
(i) => i.codes.contains(codecStr2),
|
||||
),
|
||||
];
|
||||
_setting.put(
|
||||
SettingBoxKey.preferCodecs,
|
||||
codecs.map((i) => i.name).toList(),
|
||||
);
|
||||
return codecs;
|
||||
}
|
||||
|
||||
static String get secondDecode => _setting.get(
|
||||
SettingBoxKey.secondDecode,
|
||||
defaultValue: VideoDecodeFormatType.AV1.codes.first,
|
||||
);
|
||||
final codecs = _setting.get(SettingBoxKey.preferCodecs);
|
||||
if (codecs is List && codecs.isNotEmpty) {
|
||||
return codecs.map((i) => VideoDecodeFormatType.values.byName(i)).toList();
|
||||
}
|
||||
return const [];
|
||||
}
|
||||
|
||||
static String get hardwareDecoding => _setting.get(
|
||||
SettingBoxKey.hardwareDecoding,
|
||||
@@ -598,7 +615,7 @@ abstract final class Pref {
|
||||
_setting.get(SettingBoxKey.showPgcTimeline, defaultValue: true);
|
||||
|
||||
static num get maxCacheSize =>
|
||||
_setting.get(SettingBoxKey.maxCacheSize) ?? pow(1024, 3);
|
||||
_setting.get(SettingBoxKey.maxCacheSize) ?? 1 << 30;
|
||||
|
||||
static bool get optTabletNav =>
|
||||
_setting.get(SettingBoxKey.optTabletNav, defaultValue: true);
|
||||
@@ -717,9 +734,6 @@ abstract final class Pref {
|
||||
!Platform.isIOS &&
|
||||
_setting.get(SettingBoxKey.dynamicColor, defaultValue: true);
|
||||
|
||||
static bool get autoClearCache =>
|
||||
_setting.get(SettingBoxKey.autoClearCache, defaultValue: false);
|
||||
|
||||
static bool get enableSystemProxy =>
|
||||
_setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/models/common/video/cdn_type.dart';
|
||||
import 'package:PiliPlus/models/common/video/video_decode_type.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
|
||||
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
@@ -93,4 +94,28 @@ abstract final class VideoUtils {
|
||||
final urlInfo = e.urlInfo.getOrFirst(index);
|
||||
return (liveCdnUrl ?? urlInfo.host) + e.baseUrl + urlInfo.extra;
|
||||
}
|
||||
|
||||
static VideoDecodeFormatType selectCodec(
|
||||
Iterable<String> codecs,
|
||||
List<VideoDecodeFormatType> preferCodecs,
|
||||
) {
|
||||
if (preferCodecs.isNotEmpty) {
|
||||
int bestIndex = preferCodecs.length;
|
||||
for (final e in codecs) {
|
||||
for (int i = 0; i < bestIndex; i++) {
|
||||
if (preferCodecs[i].codes.any(e.startsWith)) {
|
||||
bestIndex = i;
|
||||
if (bestIndex == 0) {
|
||||
return preferCodecs[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestIndex < preferCodecs.length) {
|
||||
return preferCodecs[bestIndex];
|
||||
}
|
||||
}
|
||||
return VideoDecodeFormatType.fromString(codecs.first);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user