mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-16 06:03:54 +08:00
tweaks (#2032)
* fix: 1080p * opt: import export * opt: downloader * opt: skeleton * opt: parseColor * tweak * opt: sb seek * opt: rxn
This commit is contained in:
@@ -1,188 +1,62 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Skeleton extends StatelessWidget {
|
||||
class Skeleton extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const Skeleton({
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
const Skeleton({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.surface.withAlpha(10);
|
||||
final shimmerGradient = LinearGradient(
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
color,
|
||||
color,
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [
|
||||
0.1,
|
||||
0.3,
|
||||
0.5,
|
||||
0.7,
|
||||
],
|
||||
begin: const Alignment(-1.0, -0.3),
|
||||
end: const Alignment(1.0, 0.9),
|
||||
tileMode: TileMode.clamp,
|
||||
);
|
||||
return Shimmer(
|
||||
linearGradient: shimmerGradient,
|
||||
child: ShimmerLoading(
|
||||
isLoading: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
State<Skeleton> createState() => _SkeletonState();
|
||||
}
|
||||
|
||||
class Shimmer extends StatefulWidget {
|
||||
static ShimmerState? of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<ShimmerState>();
|
||||
}
|
||||
|
||||
const Shimmer({
|
||||
super.key,
|
||||
required this.linearGradient,
|
||||
this.child,
|
||||
});
|
||||
|
||||
final LinearGradient linearGradient;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
ShimmerState createState() => ShimmerState();
|
||||
}
|
||||
|
||||
class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _shimmerController;
|
||||
class _SkeletonState extends State<Skeleton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late Color color;
|
||||
final matrix = Matrix4.identity();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_shimmerController = AnimationController.unbounded(vsync: this)
|
||||
..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000));
|
||||
_controller = AnimationController.unbounded(vsync: this)
|
||||
..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000))
|
||||
..addListener(_setState);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shimmerController.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
LinearGradient get gradient => LinearGradient(
|
||||
colors: widget.linearGradient.colors,
|
||||
stops: widget.linearGradient.stops,
|
||||
begin: widget.linearGradient.begin,
|
||||
end: widget.linearGradient.end,
|
||||
transform: _SlidingGradientTransform(
|
||||
slidePercent: _shimmerController.value,
|
||||
),
|
||||
);
|
||||
|
||||
bool get isSized =>
|
||||
(context.findRenderObject() as RenderBox?)?.hasSize ?? false;
|
||||
|
||||
Size get size => (context.findRenderObject() as RenderBox).size;
|
||||
|
||||
Offset getDescendantOffset({
|
||||
required RenderBox descendant,
|
||||
Offset offset = Offset.zero,
|
||||
}) {
|
||||
final shimmerBox = context.findRenderObject() as RenderBox;
|
||||
return descendant.localToGlobal(offset, ancestor: shimmerBox);
|
||||
void _setState() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Listenable get shimmerChanges => _shimmerController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _SlidingGradientTransform extends GradientTransform {
|
||||
const _SlidingGradientTransform({
|
||||
required this.slidePercent,
|
||||
});
|
||||
|
||||
final double slidePercent;
|
||||
|
||||
@override
|
||||
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
|
||||
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
class ShimmerLoading extends StatefulWidget {
|
||||
const ShimmerLoading({
|
||||
super.key,
|
||||
required this.isLoading,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final bool isLoading;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ShimmerLoading> createState() => _ShimmerLoadingState();
|
||||
}
|
||||
|
||||
class _ShimmerLoadingState extends State<ShimmerLoading> {
|
||||
Listenable? _shimmerChanges;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_shimmerChanges != null) {
|
||||
_shimmerChanges!.removeListener(_onShimmerChange);
|
||||
}
|
||||
_shimmerChanges = Shimmer.of(context)?.shimmerChanges;
|
||||
if (_shimmerChanges != null) {
|
||||
_shimmerChanges!.addListener(_onShimmerChange);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shimmerChanges?.removeListener(_onShimmerChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onShimmerChange() {
|
||||
if (widget.isLoading) {
|
||||
setState(() {});
|
||||
}
|
||||
color = ColorScheme.of(context).surface.withAlpha(10);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.isLoading) {
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
final shimmer = Shimmer.of(context)!;
|
||||
if (!shimmer.isSized) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final shimmerSize = shimmer.size;
|
||||
final gradient = shimmer.gradient;
|
||||
final offsetWithinShimmer = shimmer.getDescendantOffset(
|
||||
descendant: context.findRenderObject() as RenderBox,
|
||||
);
|
||||
|
||||
final colors = [Colors.transparent, color, color, Colors.transparent];
|
||||
return ShaderMask(
|
||||
blendMode: BlendMode.srcATop,
|
||||
shaderCallback: (bounds) {
|
||||
return gradient.createShader(
|
||||
Rect.fromLTWH(
|
||||
-offsetWithinShimmer.dx,
|
||||
-offsetWithinShimmer.dy,
|
||||
shimmerSize.width,
|
||||
shimmerSize.height,
|
||||
),
|
||||
shaderCallback: (Rect bounds) {
|
||||
final width = bounds.width;
|
||||
final height = bounds.height;
|
||||
matrix[12] = width * _controller.value;
|
||||
return ui.Gradient.linear(
|
||||
Offset(0, 0.35 * height),
|
||||
Offset(width, 0.95 * height),
|
||||
colors,
|
||||
const [0.1, 0.3, 0.5, 0.7],
|
||||
TileMode.clamp,
|
||||
matrix.storage,
|
||||
);
|
||||
},
|
||||
child: widget.child,
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:convert' show utf8, jsonDecode;
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/storage_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@@ -46,87 +46,91 @@ Future<void> importFromClipBoard<T>(
|
||||
bool showConfirmDialog = true,
|
||||
}) async {
|
||||
final data = await Clipboard.getData('text/plain');
|
||||
if (data?.text?.isNotEmpty != true) {
|
||||
SmartDialog.showToast('剪贴板无数据');
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
final text = data!.text!;
|
||||
late final T json;
|
||||
late final String formatText;
|
||||
try {
|
||||
json = jsonDecode(text);
|
||||
formatText = Utils.jsonEncoder.convert(json);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('解析json失败:$e');
|
||||
return;
|
||||
}
|
||||
bool? executeImport;
|
||||
if (showConfirmDialog) {
|
||||
final highlight = Highlight()..registerLanguage('json', langJson);
|
||||
final result = highlight.highlight(
|
||||
code: formatText,
|
||||
language: 'json',
|
||||
);
|
||||
late TextSpanRenderer renderer;
|
||||
bool? isDarkMode;
|
||||
executeImport = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final isDark = context.isDarkMode;
|
||||
if (isDark != isDarkMode) {
|
||||
isDarkMode = isDark;
|
||||
renderer = TextSpanRenderer(
|
||||
const TextStyle(),
|
||||
isDark ? githubDarkTheme : githubTheme,
|
||||
);
|
||||
result.render(renderer);
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text('是否导入如下$title?'),
|
||||
content: SingleChildScrollView(
|
||||
child: Text.rich(renderer.span!),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
if (data?.text case final text? when (text.isNotEmpty)) {
|
||||
if (!context.mounted) return;
|
||||
final T json;
|
||||
final String formatText;
|
||||
try {
|
||||
json = jsonDecode(text);
|
||||
formatText = Utils.jsonEncoder.convert(json);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('解析json失败:$e');
|
||||
return;
|
||||
}
|
||||
bool? executeImport;
|
||||
if (showConfirmDialog) {
|
||||
final highlight = Highlight()..registerLanguage('json', langJson);
|
||||
final result = highlight.highlight(
|
||||
code: formatText,
|
||||
language: 'json',
|
||||
);
|
||||
late TextSpanRenderer renderer;
|
||||
bool? isDarkMode;
|
||||
executeImport = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness.isDark;
|
||||
if (isDark != isDarkMode) {
|
||||
isDarkMode = isDark;
|
||||
renderer = TextSpanRenderer(
|
||||
const TextStyle(),
|
||||
isDark ? githubDarkTheme : githubTheme,
|
||||
);
|
||||
result.render(renderer);
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text('是否导入如下$title?'),
|
||||
content: SingleChildScrollView(
|
||||
child: Text.rich(renderer.span!),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: true),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
executeImport = true;
|
||||
}
|
||||
if (executeImport ?? false) {
|
||||
try {
|
||||
await onImport(json);
|
||||
SmartDialog.showToast('导入成功');
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: true),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
executeImport = true;
|
||||
}
|
||||
if (executeImport ?? false) {
|
||||
try {
|
||||
await onImport(json);
|
||||
SmartDialog.showToast('导入成功');
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast('剪贴板无数据');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> importFromLocalFile<T>({
|
||||
required FutureOr<void> Function(T json) onImport,
|
||||
}) async {
|
||||
final result = await FilePicker.pickFiles();
|
||||
final result = await FilePicker.pickFiles(
|
||||
type: .custom,
|
||||
allowedExtensions: const ['json', 'txt'],
|
||||
);
|
||||
if (result != null) {
|
||||
final path = result.files.first.path;
|
||||
if (path != null) {
|
||||
final data = await File(path).readAsString();
|
||||
late final T json;
|
||||
final T json;
|
||||
try {
|
||||
json = jsonDecode(data);
|
||||
} catch (e) {
|
||||
@@ -172,7 +176,6 @@ void importFromInput<T>(
|
||||
json = jsonDecode(value!) as T;
|
||||
return null;
|
||||
} catch (e) {
|
||||
if (e is FormatException) {}
|
||||
return '解析json失败:$e';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget selectMask(
|
||||
ThemeData theme,
|
||||
ColorScheme colorScheme,
|
||||
bool checked, {
|
||||
BorderRadiusGeometry borderRadius = Style.mdRadius,
|
||||
}) {
|
||||
@@ -23,12 +23,12 @@ Widget selectMask(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface.withValues(alpha: 0.8),
|
||||
color: colorScheme.surface.withValues(alpha: 0.8),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: theme.colorScheme.primary,
|
||||
color: colorScheme.primary,
|
||||
semanticLabel: '取消选择',
|
||||
),
|
||||
),
|
||||
|
||||
@@ -171,6 +171,8 @@ abstract final class VideoHttp {
|
||||
required VideoType videoType,
|
||||
String? language,
|
||||
}) async {
|
||||
final dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
final dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
final params = await WbiSign.makSign({
|
||||
'avid': ?avid,
|
||||
'bvid': ?bvid,
|
||||
@@ -188,6 +190,10 @@ abstract final class VideoHttp {
|
||||
'web_location': 1315873,
|
||||
// 免登录查看1080p
|
||||
if (tryLook) 'try_look': 1,
|
||||
'dm_img_list': '[]',
|
||||
'dm_img_str': dmImgStr,
|
||||
'dm_cover_img_str': dmCoverImgStr,
|
||||
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
|
||||
'cur_language': ?language,
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class ArticleController extends CommonDynController {
|
||||
final RxBool isLoaded = false.obs;
|
||||
DynamicItemModel? opusData; // 标题信息从summary获取, 动态没有favorite
|
||||
ArticleViewData? articleData;
|
||||
final Rx<ModuleStatModel?> stats = Rx<ModuleStatModel?>(null);
|
||||
final stats = Rxn<ModuleStatModel>();
|
||||
|
||||
List<ArticleContentModel>? get opus =>
|
||||
opusData?.modules.moduleContent ?? articleData?.opus?.content;
|
||||
|
||||
@@ -17,7 +17,7 @@ class ArticleListController
|
||||
queryData();
|
||||
}
|
||||
|
||||
Rx<ArticleListInfo?> list = Rx<ArticleListInfo?>(null);
|
||||
final list = Rxn<ArticleListInfo>();
|
||||
Owner? author;
|
||||
|
||||
@override
|
||||
|
||||
@@ -103,16 +103,16 @@ class _DownloadDetailPageState extends State<DownloadDetailPage>
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: () async {
|
||||
final allChecked = this.allChecked.toSet();
|
||||
final futures = allChecked
|
||||
.map(
|
||||
(e) => _downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
handleSelect();
|
||||
final res = await Future.wait(
|
||||
allChecked.map(
|
||||
(e) => _downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
final res = await Future.wait(futures);
|
||||
if (res.every((e) => e)) {
|
||||
SmartDialog.showToast('更新成功');
|
||||
} else {
|
||||
@@ -201,22 +201,20 @@ class _DownloadDetailPageState extends State<DownloadDetailPage>
|
||||
title: const Text('确定删除选中视频?'),
|
||||
onConfirm: () async {
|
||||
SmartDialog.showLoading();
|
||||
final watchProgress = GStorage.watchProgress;
|
||||
final allChecked = this.allChecked.toSet();
|
||||
final allChecked = this.allChecked.toList();
|
||||
final isDeleteAll = allChecked.length == _downloadItems.length;
|
||||
if (isDeleteAll) {
|
||||
await _closeSub();
|
||||
}
|
||||
for (final entry in allChecked) {
|
||||
await watchProgress.deleteAll(
|
||||
await Future.wait([
|
||||
if (isDeleteAll) _closeSub(),
|
||||
GStorage.watchProgress.deleteAll(
|
||||
allChecked.map((e) => e.cid.toString()),
|
||||
);
|
||||
await _downloadService.deleteDownload(
|
||||
entry: entry,
|
||||
removeList: true,
|
||||
refresh: false,
|
||||
);
|
||||
}
|
||||
),
|
||||
for (final entry in allChecked)
|
||||
_downloadService.deleteDownload(
|
||||
entry: entry,
|
||||
removeList: true,
|
||||
refresh: false,
|
||||
),
|
||||
]);
|
||||
_downloadService.flagNotifier.refresh();
|
||||
if (isDeleteAll) {
|
||||
SmartDialog.dismiss();
|
||||
|
||||
@@ -269,7 +269,10 @@ class DetailItem extends StatelessWidget {
|
||||
type: PBadgeType.gray,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(theme, checked ?? entry.checked),
|
||||
child: selectMask(
|
||||
theme.colorScheme,
|
||||
checked ?? entry.checked,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -57,16 +57,16 @@ class _DownloadSearchPageState
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(visualDensity: VisualDensity.compact),
|
||||
onPressed: () async {
|
||||
final allChecked = controller.allChecked.toSet();
|
||||
final future = controller.allChecked
|
||||
.map(
|
||||
(e) => _downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
controller.handleSelect();
|
||||
final res = await Future.wait(
|
||||
allChecked.map(
|
||||
(e) => _downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
final res = await Future.wait(future);
|
||||
if (res.every((e) => e)) {
|
||||
SmartDialog.showToast('更新成功');
|
||||
} else {
|
||||
|
||||
@@ -11,13 +11,13 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/scaffold.dart';
|
||||
import 'package:PiliPlus/common/widgets/select_mask.dart';
|
||||
import 'package:PiliPlus/models/common/badge_type.dart';
|
||||
import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart';
|
||||
import 'package:PiliPlus/models_new/download/download_info.dart';
|
||||
import 'package:PiliPlus/pages/download/controller.dart';
|
||||
import 'package:PiliPlus/pages/download/detail/view.dart';
|
||||
import 'package:PiliPlus/pages/download/detail/widgets/item.dart';
|
||||
import 'package:PiliPlus/pages/download/search/view.dart';
|
||||
import 'package:PiliPlus/services/download/download_service.dart';
|
||||
import 'package:PiliPlus/utils/cache_manager.dart';
|
||||
import 'package:PiliPlus/utils/extension/iterable_ext.dart' show IterableExt;
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
@@ -67,20 +67,16 @@ class _DownloadPageState extends State<DownloadPage> {
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: () async {
|
||||
final allChecked = _controller.allChecked.toSet();
|
||||
final future = [
|
||||
for (final page in _controller.allChecked)
|
||||
for (final e in page.entries)
|
||||
_downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
];
|
||||
_controller.handleSelect();
|
||||
final list = <BiliDownloadEntryInfo>[];
|
||||
for (final page in allChecked) {
|
||||
list.addAll(page.entries);
|
||||
}
|
||||
final res = await Future.wait(
|
||||
list.map(
|
||||
(e) => _downloadService.downloadDanmaku(
|
||||
entry: e,
|
||||
isUpdate: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
final res = await Future.wait(future);
|
||||
if (res.every((e) => e)) {
|
||||
SmartDialog.showToast('更新成功');
|
||||
} else {
|
||||
@@ -355,7 +351,7 @@ class _DownloadPageState extends State<DownloadPage> {
|
||||
top: 6.0,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(theme, pageInfo.checked),
|
||||
child: selectMask(theme.colorScheme, pageInfo.checked),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -381,17 +377,14 @@ class _DownloadPageState extends State<DownloadPage> {
|
||||
crossAxisAlignment: .end,
|
||||
mainAxisAlignment: .spaceBetween,
|
||||
children: [
|
||||
if (first.ownerName case final ownerName?)
|
||||
Text(
|
||||
ownerName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.6,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
)
|
||||
else
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${CacheManager.formatSize(pageInfo.entries.fold(0, (p, n) => p + n.totalBytes))} ${first.ownerName ?? ""}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.6,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
pageInfo.entries.first.moreBtn(theme),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -104,8 +104,8 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
||||
late final Rx<Pair<int, String>?> _topic;
|
||||
late final Rx<ReplyOptionType> _replyOption;
|
||||
late final TextEditingController _titleEditCtr;
|
||||
late final Rx<DateTime?> _publishTime = Rx<DateTime?>(null);
|
||||
final Rx<ReserveInfoData?> _reserveCard = Rx<ReserveInfoData?>(null);
|
||||
late final _publishTime = Rxn<DateTime>();
|
||||
final _reserveCard = Rxn<ReserveInfoData>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
||||
@@ -18,13 +18,13 @@ class DynTopicController
|
||||
|
||||
int sortBy = 0;
|
||||
String offset = '';
|
||||
Rx<TopicSortByConf?> topicSortByConf = Rx<TopicSortByConf?>(null);
|
||||
final topicSortByConf = Rxn<TopicSortByConf>();
|
||||
|
||||
double? appbarOffset;
|
||||
|
||||
// top
|
||||
Rx<bool?> isFav = Rx<bool?>(null);
|
||||
Rx<bool?> isLike = Rx<bool?>(null);
|
||||
final isFav = false.obs;
|
||||
final isLike = false.obs;
|
||||
Rx<LoadingState<TopDetails?>> topState =
|
||||
LoadingState<TopDetails?>.loading().obs;
|
||||
|
||||
@@ -42,8 +42,8 @@ class DynTopicController
|
||||
if (topState.value case Success(:final response)) {
|
||||
final topicItem = response!.topicItem!;
|
||||
topicName = topicItem.name;
|
||||
isFav.value = topicItem.isFav;
|
||||
isLike.value = topicItem.isLike;
|
||||
isFav.value = topicItem.isFav ?? false;
|
||||
isLike.value = topicItem.isLike ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class DynTopicController
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
bool isFav = this.isFav.value ?? false;
|
||||
final isFav = this.isFav.value;
|
||||
final res = isFav
|
||||
? await FavHttp.delFavTopic(topicId)
|
||||
: await FavHttp.addFavTopic(topicId);
|
||||
@@ -117,7 +117,7 @@ class DynTopicController
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
bool isLike = this.isLike.value ?? false;
|
||||
final isLike = this.isLike.value;
|
||||
final res = await FavHttp.likeTopic(topicId, isLike);
|
||||
if (res.isSuccess) {
|
||||
if (isLike) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
final padding = MediaQuery.viewPaddingOf(context);
|
||||
return scaffold(
|
||||
body: Stack(
|
||||
@@ -60,7 +60,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
slivers: [
|
||||
Obx(
|
||||
() => _buildAppBar(
|
||||
theme,
|
||||
colorScheme,
|
||||
padding,
|
||||
_controller.topState.value,
|
||||
),
|
||||
@@ -70,7 +70,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
_controller.topicSortByConf.value?.allSortBy;
|
||||
if (allSortBy != null && allSortBy.isNotEmpty) {
|
||||
return SliverPinnedHeader(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
backgroundColor: colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 12 + padding.left,
|
||||
@@ -80,9 +80,8 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return ToggleButtons(
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
selectedColor:
|
||||
theme.colorScheme.onSecondaryContainer,
|
||||
fillColor: colorScheme.secondaryContainer,
|
||||
selectedColor: colorScheme.onSecondaryContainer,
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 54,
|
||||
minHeight: 24,
|
||||
@@ -159,7 +158,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
}
|
||||
|
||||
Widget _buildAppBar(
|
||||
ThemeData theme,
|
||||
ColorScheme colorScheme,
|
||||
EdgeInsets padding,
|
||||
LoadingState<TopDetails?> topState,
|
||||
) {
|
||||
@@ -216,7 +215,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
),
|
||||
Text(
|
||||
' 发起',
|
||||
style: TextStyle(color: theme.colorScheme.outline),
|
||||
style: TextStyle(color: colorScheme.outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -232,7 +231,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
const SizedBox(height: 6),
|
||||
SelectableText(
|
||||
response.topicItem!.description!,
|
||||
style: TextStyle(color: theme.colorScheme.onSurfaceVariant),
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
@@ -241,7 +240,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
'${NumUtils.numFormat(response.topicItem!.view)}浏览 · ${NumUtils.numFormat(response.topicItem!.discuss)}讨论',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
@@ -249,13 +248,11 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1,
|
||||
color: theme.colorScheme.outline.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
color: colorScheme.outline.withValues(alpha: 0.2),
|
||||
),
|
||||
foregroundColor: _controller.isLike.value == true
|
||||
foregroundColor: _controller.isLike.value
|
||||
? null
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
: colorScheme.onSurfaceVariant,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
@@ -264,7 +261,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: _controller.onLike,
|
||||
icon: _controller.isLike.value == true
|
||||
icon: _controller.isLike.value
|
||||
? const Icon(FontAwesomeIcons.solidThumbsUp, size: 13)
|
||||
: const Icon(FontAwesomeIcons.thumbsUp, size: 13),
|
||||
label: Text(
|
||||
@@ -278,22 +275,17 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1,
|
||||
color: theme.colorScheme.outline.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
color: colorScheme.outline.withValues(alpha: 0.2),
|
||||
),
|
||||
foregroundColor: _controller.isFav.value == true
|
||||
foregroundColor: _controller.isFav.value
|
||||
? null
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
: colorScheme.onSurfaceVariant,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
visualDensity: const .new(horizontal: -4, vertical: -4),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: _controller.onFav,
|
||||
icon: _controller.isFav.value == true
|
||||
icon: _controller.isFav.value
|
||||
? const Icon(FontAwesomeIcons.solidStar, size: 13)
|
||||
: const Icon(FontAwesomeIcons.star, size: 13),
|
||||
label: Text(
|
||||
@@ -322,7 +314,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
PopupMenuItem(
|
||||
onTap: _controller.onFav,
|
||||
child: Text(
|
||||
'${_controller.isFav.value == true ? '取消' : ''}收藏',
|
||||
'${_controller.isFav.value ? '取消' : ''}收藏',
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
@@ -333,7 +325,7 @@ class _DynTopicPageState extends State<DynTopicPage> with DynMixin {
|
||||
return;
|
||||
}
|
||||
WebViewPage.toWebView(
|
||||
'https://www.bilibili.com/h5/topic-active/topic-report?topic_id=${_controller.topicId}&topic_name=${_controller.topicName}&${ThemeUtils.themeUrl(theme.isDark)}',
|
||||
'https://www.bilibili.com/h5/topic-active/topic-report?topic_id=${_controller.topicId}&topic_name=${_controller.topicName}&${ThemeUtils.themeUrl(colorScheme.isDark)}',
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ class FavNoteItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
@@ -68,7 +68,7 @@ class FavNoteItem extends StatelessWidget {
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(
|
||||
theme,
|
||||
colorScheme,
|
||||
item.checked,
|
||||
),
|
||||
),
|
||||
@@ -97,7 +97,7 @@ class FavNoteItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1,
|
||||
color: theme.colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
@@ -107,7 +107,7 @@ class FavNoteItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1,
|
||||
color: theme.colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -34,7 +34,7 @@ class FavPgcItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
@@ -86,7 +86,7 @@ class FavPgcItem extends StatelessWidget {
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(
|
||||
theme,
|
||||
colorScheme,
|
||||
item.checked,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
@@ -111,7 +111,7 @@ class FavPgcItem extends StatelessWidget {
|
||||
'${item.newEp!.indexShow}${item.isFinish == 0 && item.renewalTime?.isNotEmpty == true ? ',${item.renewalTime}' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -123,7 +123,7 @@ class FavPgcItem extends StatelessWidget {
|
||||
item.progress!,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -141,7 +141,7 @@ class FavPgcItem extends StatelessWidget {
|
||||
iconSize: 18,
|
||||
onPressed: onUpdateStatus,
|
||||
icon: const Icon(Icons.more_vert),
|
||||
iconColor: theme.colorScheme.outline,
|
||||
iconColor: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -80,11 +80,11 @@ class FavDetailController
|
||||
late int mediaId;
|
||||
late String heroTag;
|
||||
final Rx<FavFolderInfo> folderInfo = FavFolderInfo().obs;
|
||||
final Rx<bool?> _isOwner = Rx<bool?>(null);
|
||||
final RxBool _isOwner = false.obs;
|
||||
final Rx<FavOrderType> order = FavOrderType.mtime.obs;
|
||||
|
||||
@override
|
||||
bool get isOwner => _isOwner.value ?? false;
|
||||
bool get isOwner => _isOwner.value;
|
||||
|
||||
late final account = Accounts.main;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final isOwner = !isSort && ctr!.isOwner;
|
||||
late final enableMultiSelect = ctr?.enableMultiSelect.value ?? false;
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
|
||||
final onLongPress = isSort || enableMultiSelect
|
||||
? null
|
||||
@@ -135,7 +135,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
if (!isSort)
|
||||
Positioned.fill(
|
||||
child: selectMask(
|
||||
theme,
|
||||
colorScheme,
|
||||
item.checked,
|
||||
),
|
||||
),
|
||||
@@ -145,7 +145,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
content(context, theme, isOwner),
|
||||
content(context, colorScheme, isOwner),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -153,7 +153,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget content(BuildContext context, ThemeData theme, isOwner) {
|
||||
Widget content(BuildContext context, ColorScheme colorScheme, bool isOwner) {
|
||||
return Expanded(
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
@@ -178,7 +178,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
@@ -189,7 +189,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (item.type != 24)
|
||||
@@ -215,7 +215,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
child: iconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
tooltip: '取消收藏',
|
||||
iconColor: theme.colorScheme.outline,
|
||||
iconColor: colorScheme.outline,
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
@@ -226,7 +226,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: theme.colorScheme.outline),
|
||||
style: TextStyle(color: colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
|
||||
@@ -177,7 +177,10 @@ class HistoryItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(theme, item.checked),
|
||||
child: selectMask(
|
||||
theme.colorScheme,
|
||||
item.checked,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -158,7 +158,7 @@ class VideoCardHLater extends StatelessWidget {
|
||||
),
|
||||
Positioned.fill(
|
||||
child: selectMask(
|
||||
theme,
|
||||
theme.colorScheme,
|
||||
videoItem.checked,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,7 +13,7 @@ class LiveFollowController
|
||||
queryData();
|
||||
}
|
||||
|
||||
Rx<int?> count = Rx<int?>(null);
|
||||
final count = RxnInt();
|
||||
|
||||
@override
|
||||
void checkIsEnd(int length) {
|
||||
|
||||
@@ -50,14 +50,14 @@ class LiveRoomController extends GetxController {
|
||||
int roomId = Get.arguments;
|
||||
int? ruid;
|
||||
DanmakuController<DanmakuExtra>? danmakuController;
|
||||
PlPlayerController plPlayerController = PlPlayerController.getInstance(
|
||||
final plPlayerController = PlPlayerController.getInstance(
|
||||
isLive: true,
|
||||
);
|
||||
|
||||
RxBool isLoaded = false.obs;
|
||||
Rx<RoomInfoH5Data?> roomInfoH5 = Rx<RoomInfoH5Data?>(null);
|
||||
final isLoaded = false.obs;
|
||||
final roomInfoH5 = Rxn<RoomInfoH5Data>();
|
||||
|
||||
Rx<int?> liveTime = Rx<int?>(null);
|
||||
final liveTime = Rxn<int>();
|
||||
Timer? liveTimeTimer;
|
||||
|
||||
void startLiveTimer() {
|
||||
@@ -100,11 +100,11 @@ class LiveRoomController extends GetxController {
|
||||
LiveDmInfoData? dmInfo;
|
||||
List<RichTextItem>? savedDanmaku;
|
||||
int builtLength = 0;
|
||||
RxList<dynamic> messages = <dynamic>[].obs;
|
||||
final messages = <dynamic>[].obs;
|
||||
bool get shouldRefresh => builtLength != messages.length;
|
||||
late final Rx<SuperChatItem?> fsSC = Rx<SuperChatItem?>(null);
|
||||
late final fsSC = Rxn<SuperChatItem>();
|
||||
late final RxList<SuperChatItem> superChatMsg = <SuperChatItem>[].obs;
|
||||
RxBool disableAutoScroll = false.obs;
|
||||
final disableAutoScroll = false.obs;
|
||||
bool autoScroll = true;
|
||||
LiveMessageStream? _msgStream;
|
||||
late final ScrollController scrollController;
|
||||
@@ -112,7 +112,7 @@ class LiveRoomController extends GetxController {
|
||||
late final PageController pageController;
|
||||
|
||||
int? currentQn = PlatformUtils.isMobile ? null : Pref.liveQuality;
|
||||
RxString currentQnDesc = ''.obs;
|
||||
final currentQnDesc = ''.obs;
|
||||
final RxBool isPortrait = false.obs;
|
||||
late List<({int code, String desc})> acceptQnList = [];
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ class PgcReviewController
|
||||
final PgcReviewType type;
|
||||
final dynamic mediaId;
|
||||
|
||||
Rx<int?> count = Rx<int?>(null);
|
||||
final count = RxnInt();
|
||||
String? next;
|
||||
Rx<PgcReviewSortType> sortType = PgcReviewSortType.def.obs;
|
||||
final sortType = PgcReviewSortType.def.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
||||
@@ -14,7 +14,7 @@ class PopularSeriesController
|
||||
with ReloadMixin {
|
||||
late int number;
|
||||
|
||||
final Rx<PopularSeriesConfig?> config = Rx<PopularSeriesConfig?>(null);
|
||||
final config = Rxn<PopularSeriesConfig>();
|
||||
String? reminder;
|
||||
List<PopularSeriesListItem>? seriesList;
|
||||
|
||||
|
||||
@@ -741,8 +741,8 @@ class VideoDetailController extends GetxController
|
||||
|
||||
bool isQuerying = false;
|
||||
|
||||
final Rx<List<LanguageItem>?> languages = Rx<List<LanguageItem>?>(null);
|
||||
final Rx<String?> currLang = Rx<String?>(null);
|
||||
final languages = Rxn<List<LanguageItem>>();
|
||||
final currLang = Rxn<String>();
|
||||
void setLanguage(String language) {
|
||||
if (currLang.value == language) return;
|
||||
if (!isLoginVideo) {
|
||||
|
||||
@@ -467,14 +467,21 @@ class _PostPanelState extends State<PostPanel>
|
||||
final player = plPlayerController.videoPlayerController;
|
||||
if (player != null) {
|
||||
final start = (item.segment.first * 1000).round();
|
||||
Future<void> seekTo() => player.seek(
|
||||
Duration(milliseconds: (item.segment.second * 1000).round()),
|
||||
);
|
||||
if (start <= 0) {
|
||||
seekTo();
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
return;
|
||||
}
|
||||
final seek = max(0, start - 2000);
|
||||
await player.seek(Duration(milliseconds: seek));
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
Future<void> seekTo() => player.seek(
|
||||
Duration(milliseconds: (item.segment.second * 1000).round()),
|
||||
);
|
||||
if (start > seek) {
|
||||
final posSub = player.stream.position.listen(
|
||||
null,
|
||||
|
||||
@@ -32,7 +32,7 @@ class VideoReplyReplyController extends ReplyController
|
||||
int replyType;
|
||||
|
||||
bool hasRoot = false;
|
||||
final Rx<ReplyInfo?> firstFloor = Rx(null);
|
||||
final firstFloor = Rxn<ReplyInfo>();
|
||||
|
||||
final index = RxnInt();
|
||||
|
||||
|
||||
@@ -645,7 +645,10 @@ class HeaderControlState extends State<HeaderControl>
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
try {
|
||||
final result = await FilePicker.pickFiles();
|
||||
final result = await FilePicker.pickFiles(
|
||||
type: .custom,
|
||||
allowedExtensions: const ['json', 'vtt', 'srt'],
|
||||
);
|
||||
if (result != null) {
|
||||
final file = result.files.single;
|
||||
final path = file.path;
|
||||
|
||||
@@ -2,11 +2,12 @@ import 'package:flutter/rendering.dart' show Color;
|
||||
|
||||
abstract final class ColourUtils {
|
||||
static Color parseColor(String color) =>
|
||||
Color(int.parse('FF${color.substring(1)}', radix: 16));
|
||||
Color(0xFF000000 | int.parse(color.substring(1), radix: 16));
|
||||
|
||||
static Color parseMedalColor(String color) => Color(
|
||||
int.parse('${color.substring(7)}${color.substring(1, 7)}', radix: 16),
|
||||
);
|
||||
static Color parseMedalColor(String color) {
|
||||
final rgba = int.parse(color.substring(1), radix: 16);
|
||||
return Color.fromARGB(rgba, rgba >> 24, rgba >> 16, rgba >> 8);
|
||||
}
|
||||
|
||||
static Color index2Color(int index, Color color) => switch (index) {
|
||||
0 => const Color(0xFFfdad13),
|
||||
|
||||
Reference in New Issue
Block a user