Compare commits

...

12 Commits

Author SHA1 Message Date
dom
e293083492 opt set shader
opt refresh player

Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-15 09:48:31 +08:00
dom
7f39f36c75 fix auto fullscreen
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-15 09:16:21 +08:00
dom
565819febe remove unused param
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-15 09:16:21 +08:00
dom
af150118a1 opt load saved reply
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-15 09:16:21 +08:00
dom
470e519a2b opt save panel
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-15 09:16:21 +08:00
HeXis-YS
d73588f1fd fix(player): disable audio normalization on missing audio input (#1865)
* fix(player): disable audio normalization on missing audio input

* fix(player): skip normalization when local audio input is missing

* fix `hasDashAudio`

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2026-03-15 09:16:10 +08:00
dom
ffbbd8e702 Release 2.0.1
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 17:52:15 +08:00
dom
a1815c4cc7 unify platform file name
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 17:44:10 +08:00
dom
b9e543f26b improve export/import
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 17:09:23 +08:00
dom
0788a4de2d upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 13:40:58 +08:00
dom
b0c6e2f5cd fix dyn folded link
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 13:40:58 +08:00
dom
9489d8a7ca opt handle dyn additional
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-14 12:19:30 +08:00
22 changed files with 775 additions and 591 deletions

View File

@@ -0,0 +1,277 @@
import 'dart:async' show FutureOr;
import 'dart:convert' show utf8, jsonDecode;
import 'dart:io' show File;
import 'package:PiliPlus/common/constants.dart' show StyleString;
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show Clipboard;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:intl/intl.dart' show DateFormat;
import 'package:re_highlight/languages/json.dart';
import 'package:re_highlight/re_highlight.dart';
import 'package:re_highlight/styles/base16/github.dart';
import 'package:re_highlight/styles/github-dark.dart';
void exportToClipBoard({
required ValueGetter<String> onExport,
}) {
Utils.copyText(onExport());
}
void exportToLocalFile({
required ValueGetter<String> onExport,
required ValueGetter<String> localFileName,
}) {
final res = utf8.encode(onExport());
Utils.saveBytes2File(
name:
'piliplus_${localFileName()}_'
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json',
bytes: res,
allowedExtensions: const ['json'],
);
}
Future<void> importFromClipBoard<T>(
BuildContext context, {
required String title,
required ValueGetter<String> onExport,
required FutureOr<void> Function(T json) onImport,
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,
),
),
),
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');
}
}
}
Future<void> importFromLocalFile<T>({
required FutureOr<void> Function(T json) onImport,
}) async {
final result = await FilePicker.pickFiles();
if (result != null) {
final path = result.files.first.path;
if (path != null) {
final data = await File(path).readAsString();
late final T json;
try {
json = jsonDecode(data);
} catch (e) {
SmartDialog.showToast('解析json失败$e');
return;
}
try {
await onImport(json);
SmartDialog.showToast('导入成功');
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
}
}
}
void importFromInput<T>(
BuildContext context, {
required String title,
required FutureOr<void> Function(T json) onImport,
}) {
final key = GlobalKey<FormFieldState<String>>();
late T json;
String? forceErrorText;
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('输入$title'),
constraints: StyleString.dialogFixedConstraints,
content: TextFormField(
key: key,
minLines: 4,
maxLines: 12,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
errorMaxLines: 3,
),
validator: (value) {
if (forceErrorText != null) return forceErrorText;
try {
json = jsonDecode(value!) as T;
return null;
} catch (e) {
if (e is FormatException) {}
return '解析json失败$e';
}
},
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
if (key.currentState?.validate() == true) {
try {
await onImport(json);
Get.back();
SmartDialog.showToast('导入成功');
return;
} catch (e) {
forceErrorText = '导入失败:$e';
}
key.currentState?.validate();
forceErrorText = null;
}
},
child: const Text('确定'),
),
],
),
);
}
Future<void> showImportExportDialog<T>(
BuildContext context, {
required String title,
required ValueGetter<String> onExport,
required FutureOr<void> Function(T json) onImport,
required ValueGetter<String> localFileName,
}) => showDialog(
context: context,
builder: (context) {
const style = TextStyle(fontSize: 15);
return SimpleDialog(
clipBehavior: Clip.hardEdge,
title: Text('导入/导出$title'),
children: [
ListTile(
dense: true,
title: const Text('导出至剪贴板', style: style),
onTap: () {
Get.back();
exportToClipBoard(onExport: onExport);
},
),
ListTile(
dense: true,
title: const Text('导出文件至本地', style: style),
onTap: () {
Get.back();
exportToLocalFile(onExport: onExport, localFileName: localFileName);
},
),
Divider(
height: 1,
color: ColorScheme.of(context).outline.withValues(alpha: 0.1),
),
ListTile(
dense: true,
title: const Text('输入', style: style),
onTap: () {
Get.back();
importFromInput<T>(context, title: title, onImport: onImport);
},
),
ListTile(
dense: true,
title: const Text('从剪贴板导入', style: style),
onTap: () {
Get.back();
importFromClipBoard<T>(
context,
title: title,
onExport: onExport,
onImport: onImport,
);
},
),
ListTile(
dense: true,
title: const Text('从本地文件导入', style: style),
onTap: () {
Get.back();
importFromLocalFile<T>(onImport: onImport);
},
),
],
);
},
);

View File

@@ -161,6 +161,7 @@ abstract final class DownloadHttp {
dashDrmType: 0,
),
];
entry.hasDashAudio = true;
}
return Type2(
duration: dash.duration!,

View File

@@ -104,6 +104,8 @@ class DynamicItemModel {
String? type;
bool? visible;
late bool linkFolded = false;
// opus
Fallback? fallback;

View File

@@ -7,7 +7,7 @@ import 'package:get/route_manager.dart';
class BiliDownloadEntryInfo with MultiSelectData {
int mediaType;
final bool hasDashAudio;
bool hasDashAudio;
bool isCompleted;
int totalBytes;
int downloadedBytes;

View File

@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/services/logger.dart';
@@ -21,15 +21,9 @@ import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/update.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide ListTile;
import 'package:flutter/services.dart' show Clipboard, ClipboardData;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:re_highlight/languages/json.dart';
import 'package:re_highlight/re_highlight.dart';
import 'package:re_highlight/styles/github-dark.dart';
import 'package:re_highlight/styles/github.dart';
class AboutPage extends StatefulWidget {
const AboutPage({super.key, this.showAppBar = true});
@@ -249,8 +243,10 @@ Commit Hash: ${BuildConfig.commitHash}''',
onTap: () => showImportExportDialog<Map>(
context,
title: '登录信息',
toJson: () => Utils.jsonEncoder.convert(Accounts.account.toMap()),
fromJson: (json) async {
localFileName: () => 'account',
onExport: () =>
Utils.jsonEncoder.convert(Accounts.account.toMap()),
onImport: (json) async {
final res = json.map(
(key, value) => MapEntry(key, LoginAccount.fromJson(value)),
);
@@ -260,7 +256,6 @@ Commit Hash: ${BuildConfig.commitHash}''',
if (Accounts.main.isLogin) {
await LoginUtils.onLoginMain();
}
return true;
},
),
),
@@ -268,12 +263,12 @@ Commit Hash: ${BuildConfig.commitHash}''',
title: const Text('导入/导出设置'),
dense: false,
leading: const Icon(Icons.import_export_outlined),
onTap: () => showImportExportDialog(
onTap: () => showImportExportDialog<Map<String, dynamic>>(
context,
title: '设置',
label: GStorage.setting.name,
toJson: GStorage.exportAllSettings,
fromJson: GStorage.importAllJsonSettings,
localFileName: () => 'setting_${context.platformName}',
onExport: GStorage.exportAllSettings,
onImport: GStorage.importAllJsonSettings,
),
),
ListTile(
@@ -302,15 +297,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
dense: true,
onTap: () async {
Get.back();
await Future.wait([
GStorage.userInfo.clear(),
GStorage.setting.clear(),
GStorage.localCache.clear(),
GStorage.video.clear(),
GStorage.historyWord.clear(),
Accounts.clear(),
GStorage.watchProgress.clear(),
]);
await GStorage.clear();
SmartDialog.showToast('重置成功');
},
title: const Text('重置所有数据(含登录信息)', style: style),
@@ -325,190 +312,3 @@ Commit Hash: ${BuildConfig.commitHash}''',
);
}
}
Future<void> showImportExportDialog<T>(
BuildContext context, {
required String title,
String? label,
required ValueGetter<String> toJson,
required FutureOr<bool> Function(T json) fromJson,
}) => showDialog(
context: context,
builder: (context) {
const style = TextStyle(fontSize: 15);
return SimpleDialog(
clipBehavior: Clip.hardEdge,
title: Text('导入/导出$title'),
children: [
if (label != null)
ListTile(
dense: true,
title: const Text('导出文件至本地', style: style),
onTap: () {
Get.back();
final res = utf8.encode(toJson());
final name =
'piliplus_${label}_${context.isTablet ? 'pad' : 'phone'}_'
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json';
Utils.saveBytes2File(
name: name,
bytes: res,
allowedExtensions: const ['json'],
);
},
),
ListTile(
dense: true,
title: Text('导出$title至剪贴板', style: style),
onTap: () {
Get.back();
Utils.copyText(toJson());
},
),
ListTile(
dense: true,
title: Text('从剪贴板导入$title', style: style),
onTap: () async {
Get.back();
ClipboardData? 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;
}
final highlight = Highlight()..registerLanguage('json', langJson);
final result = highlight.highlight(
code: formatText,
language: 'json',
);
late TextSpanRenderer renderer;
bool? isDarkMode;
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,
),
),
),
TextButton(
onPressed: () async {
Get.back();
try {
if (await fromJson(json)) {
SmartDialog.showToast('导入成功');
}
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
);
},
),
ListTile(
dense: true,
title: Text('输入$title', style: style),
onTap: () {
Get.back();
final key = GlobalKey<FormFieldState<String>>();
late T json;
String? forceErrorText;
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('输入$title'),
constraints: StyleString.dialogFixedConstraints,
content: TextFormField(
key: key,
minLines: 4,
maxLines: 12,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
errorMaxLines: 3,
),
validator: (value) {
if (forceErrorText != null) return forceErrorText;
try {
json = jsonDecode(value!) as T;
return null;
} catch (e) {
if (e is FormatException) {}
return '解析json失败$e';
}
},
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
if (key.currentState?.validate() == true) {
try {
if (await fromJson(json)) {
Get.back();
SmartDialog.showToast('导入成功');
return;
}
} catch (e) {
forceErrorText = '导入失败:$e';
}
key.currentState?.validate();
forceErrorText = null;
}
},
child: const Text('确定'),
),
],
),
);
},
),
],
);
},
);

View File

@@ -7,11 +7,10 @@ import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
Widget addWidget(
Widget? addWidget(
BuildContext context, {
required int floor,
required ThemeData theme,
@@ -27,7 +26,7 @@ Widget addWidget(
try {
switch (type) {
// 转发的投稿
case 'ADDITIONAL_TYPE_UGC':
case 'ADDITIONAL_TYPE_UGC' when (additional.ugc != null):
final ugc = additional.ugc!;
child = InkWell(
borderRadius: borderRadius,
@@ -72,7 +71,7 @@ Widget addWidget(
),
);
case 'ADDITIONAL_TYPE_RESERVE':
case 'ADDITIONAL_TYPE_RESERVE' when (additional.reserve != null):
final reserve = additional.reserve!;
if (reserve.state != -1 && reserve.title != null) {
child = InkWell(
@@ -213,7 +212,8 @@ Widget addWidget(
);
}
case 'ADDITIONAL_TYPE_UPOWER_LOTTERY':
case 'ADDITIONAL_TYPE_UPOWER_LOTTERY'
when (additional.upowerLottery != null):
final content = additional.upowerLottery!;
child = InkWell(
borderRadius: borderRadius,
@@ -308,7 +308,7 @@ Widget addWidget(
);
// 商品
case 'ADDITIONAL_TYPE_GOODS':
case 'ADDITIONAL_TYPE_GOODS' when (additional.goods != null):
final content = additional.goods!;
if (content.items?.isNotEmpty == true) {
child = Column(
@@ -395,7 +395,7 @@ Widget addWidget(
);
}
case 'ADDITIONAL_TYPE_VOTE':
case 'ADDITIONAL_TYPE_VOTE' when (additional.vote != null):
final vote = additional.vote!;
child = InkWell(
borderRadius: borderRadius,
@@ -483,7 +483,7 @@ Widget addWidget(
),
);
case 'ADDITIONAL_TYPE_COMMON':
case 'ADDITIONAL_TYPE_COMMON' when (additional.common != null):
final content = additional.common!;
child = InkWell(
borderRadius: borderRadius,
@@ -557,7 +557,7 @@ Widget addWidget(
),
);
case 'ADDITIONAL_TYPE_MATCH':
case 'ADDITIONAL_TYPE_MATCH' when (additional.match != null):
final content = additional.match!;
Widget teamItem(TTeam team, Alignment alignment, EdgeInsets padding) {
return Expanded(
@@ -687,13 +687,7 @@ Widget addWidget(
),
);
} else {
if (kDebugMode) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text('additional panel\ntype: $type'),
);
}
return const SizedBox.shrink();
return null;
}
} catch (e) {
return Padding(

View File

@@ -33,7 +33,7 @@ List<Widget> dynContent(
floor: floor,
),
if (moduleDynamic?.additional case final additional?)
addWidget(
?addWidget(
theme: theme,
context,
idStr: item.idStr,

View File

@@ -17,6 +17,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
const _linkFoldedText = '网页链接';
// 富文本
TextSpan? richNode(
BuildContext context, {
@@ -63,6 +65,9 @@ TextSpan? richNode(
for (final i in richTextNodes) {
switch (i.type) {
case 'RICH_TEXT_NODE_TYPE_TEXT':
if (i.origText == _linkFoldedText) {
item.linkFolded = true;
}
spanChildren.add(
TextSpan(
text: i.origText,
@@ -102,6 +107,10 @@ TextSpan? richNode(
break;
// 网页链接
case 'RICH_TEXT_NODE_TYPE_WEB':
final hasLink = i.jumpUrl?.isNotEmpty ?? false;
if (!hasLink) {
item.linkFolded = true;
}
spanChildren
..add(
WidgetSpan(
@@ -117,10 +126,10 @@ TextSpan? richNode(
TextSpan(
text: i.text,
style: style,
recognizer: i.origText == null
? null
: (NoDeadlineTapGestureRecognizer()
..onTap = () => PageUtils.handleWebview(i.origText!)),
recognizer: hasLink
? (NoDeadlineTapGestureRecognizer()
..onTap = () => PageUtils.handleWebview(i.jumpUrl!))
: null,
),
);
break;

View File

@@ -1,7 +1,9 @@
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show ReplyInfo;
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart';
@@ -9,9 +11,13 @@ import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/reply_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/waterfall.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:get/get_rx/get_rx.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
class MyReply extends StatefulWidget {
@@ -22,13 +28,18 @@ class MyReply extends StatefulWidget {
}
class _MyReplyState extends State<MyReply> with DynMixin {
late final List<ReplyInfo> _replies;
final List<ReplyInfo> _replies = <ReplyInfo>[];
@override
void initState() {
super.initState();
_replies = GStorage.reply!.values.map(ReplyInfo.fromBuffer).toList()
..sort((a, b) => b.ctime.compareTo(a.ctime)); // rpid not aligned
_initReply();
}
void _initReply() {
_replies
..assignAll(GStorage.reply!.values.map(ReplyInfo.fromBuffer))
..sort((a, b) => b.ctime.compareTo(a.ctime)); // rpid not aligned;
}
@override
@@ -36,24 +47,33 @@ class _MyReplyState extends State<MyReply> with DynMixin {
return Scaffold(
appBar: AppBar(
title: const Text('我的评论'),
actions: kDebugMode
? [
IconButton(
tooltip: 'Clear',
onPressed: () => showConfirmDialog(
context: context,
title: 'Clear Local Storage?',
onConfirm: () {
GStorage.reply!.clear();
_replies.clear();
setState(() {});
},
),
icon: const Icon(Icons.clear_all),
),
const SizedBox(width: 6),
]
: null,
actions: [
if (kDebugMode)
IconButton(
tooltip: 'Clear',
onPressed: () => showConfirmDialog(
context: context,
title: 'Clear Local Storage?',
onConfirm: () {
GStorage.reply!.clear();
_replies.clear();
setState(() {});
},
),
icon: const Icon(Icons.clear_all),
),
IconButton(
tooltip: '导出',
onPressed: _showExportDialog,
icon: const Icon(Icons.file_upload_outlined),
),
IconButton(
tooltip: '导入',
onPressed: _showImportDialog,
icon: const Icon(Icons.file_download_outlined),
),
const SizedBox(width: 6),
],
),
body: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
@@ -121,4 +141,89 @@ class _MyReplyState extends State<MyReply> with DynMixin {
isManual: true,
);
}
String _onExport() {
return Utils.jsonEncoder.convert(
_replies.map((e) => e.toProto3Json()).toList(),
);
}
void _showExportDialog() {
const style = TextStyle(fontSize: 14);
showDialog(
context: context,
builder: (context) => SimpleDialog(
clipBehavior: .hardEdge,
contentPadding: const .symmetric(vertical: 12),
children: [
ListTile(
dense: true,
title: const Text('导出至剪贴板', style: style),
onTap: () {
Get.back();
exportToClipBoard(onExport: _onExport);
},
),
ListTile(
dense: true,
title: const Text('导出文件至本地', style: style),
onTap: () {
Get.back();
exportToLocalFile(
onExport: _onExport,
localFileName: () => 'reply',
);
},
),
],
),
);
}
Future<void> _onImport(List<dynamic> list) async {
await GStorage.reply!.putAll({
for (var e in list)
e['id'].toString(): (ReplyInfo.create()..mergeFromProto3Json(e))
.writeToBuffer(),
});
if (mounted) {
_initReply();
setState(() {});
}
}
void _showImportDialog() {
const style = TextStyle(fontSize: 14);
showDialog(
context: context,
builder: (context) => SimpleDialog(
clipBehavior: .hardEdge,
contentPadding: const .symmetric(vertical: 12),
children: [
ListTile(
dense: true,
title: const Text('从剪贴板导入', style: style),
onTap: () {
Get.back();
importFromClipBoard<List<dynamic>>(
context,
title: '评论',
onExport: _onExport,
onImport: _onImport,
showConfirmDialog: false,
);
},
),
ListTile(
dense: true,
title: const Text('从本地文件导入', style: style),
onTap: () {
Get.back();
importFromLocalFile<List<dynamic>>(onImport: _onImport);
},
),
],
),
);
}
}

View File

@@ -336,266 +336,258 @@ class _SavePanelState extends State<SavePanel> {
final padding = MediaQuery.viewPaddingOf(context);
final maxWidth = context.mediaQueryShortestSide;
late final coverSize = MediaQuery.textScalerOf(context).scale(65);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: Get.back,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
SingleChildScrollView(
padding: EdgeInsets.only(
top: 12 + padding.top,
bottom: 80 + padding.bottom,
),
child: Listener(
return Stack(
clipBehavior: .none,
alignment: .center,
children: [
SingleChildScrollView(
hitTestBehavior: .deferToChild,
padding: .only(
top: 12 + padding.top,
bottom: 80 + padding.bottom,
),
child: Container(
width: maxWidth,
padding: const .symmetric(horizontal: 12),
child: RepaintBoundary(
key: boundaryKey,
child: Container(
width: maxWidth,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: RepaintBoundary(
key: boundaryKey,
child: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const .all(.circular(12)),
),
child: AnimatedSize(
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
duration: const Duration(milliseconds: 255),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_item case final ReplyInfo reply)
IgnorePointer(
child: ReplyItemGrpc(
replyItem: reply,
replyLevel: 0,
needDivider: false,
upMid: widget.upMid,
clipBehavior: .hardEdge,
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const .all(.circular(12)),
),
child: AnimatedSize(
curve: Curves.easeInOut,
alignment: .topCenter,
duration: const Duration(milliseconds: 255),
child: Column(
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
switch (_item) {
ReplyInfo reply => IgnorePointer(
child: ReplyItemGrpc(
replyItem: reply,
replyLevel: 0,
needDivider: false,
upMid: widget.upMid,
),
),
DynamicItemModel dyn => IgnorePointer(
child: DynamicPanel(
item: dyn,
isDetail: true,
isSave: true,
),
),
_ => throw UnsupportedError(_item.toString()),
},
if (cover?.isNotEmpty == true &&
title?.isNotEmpty == true)
Container(
height: 81,
clipBehavior: Clip.hardEdge,
margin: const .symmetric(horizontal: 12),
padding: const .all(8),
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
borderRadius: const .all(.circular(8)),
),
child: Row(
spacing: 10,
children: [
NetworkImgLayer(
src: cover!,
height: coverSize,
width: coverType == .def16_9
? coverSize * StyleString.aspectRatio16x9
: coverSize,
quality: 100,
borderRadius: const .all(.circular(6)),
),
)
else if (_item case final DynamicItemModel dyn)
IgnorePointer(
child: DynamicPanel(
item: dyn,
isDetail: true,
isSave: true,
),
),
if (cover?.isNotEmpty == true &&
title?.isNotEmpty == true)
Container(
height: 81,
clipBehavior: Clip.hardEdge,
margin: const .symmetric(horizontal: 12),
padding: const .all(8),
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
borderRadius: const .all(.circular(8)),
),
child: Row(
children: [
NetworkImgLayer(
src: cover!,
height: coverSize,
width: coverType == .def16_9
? coverSize *
StyleString.aspectRatio16x9
: coverSize,
quality: 100,
borderRadius: const .all(.circular(6)),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: .start,
children: [
Text(
'$title\n',
maxLines: 2,
overflow: .ellipsis,
Expanded(
child: Column(
crossAxisAlignment: .start,
children: [
Expanded(
child: Text(
'$title\n',
maxLines: 2,
overflow: .ellipsis,
),
),
if (pubdate != null)
Text(
DateFormatUtils.format(
pubdate,
format: dateFormat,
),
if (pubdate != null) ...[
const Spacer(),
Text(
DateFormatUtils.format(
pubdate,
format: dateFormat,
),
style: TextStyle(
color: theme.colorScheme.outline,
style: TextStyle(
color: theme.colorScheme.outline,
),
),
],
),
),
],
),
),
showBottom
? Stack(
clipBehavior: .none,
children: [
if (uri.isNotEmpty)
Align(
alignment: .centerRight,
child: Row(
children: [
Expanded(
child: Column(
mainAxisSize: .min,
crossAxisAlignment: .end,
spacing: 4,
children: [
if (uname?.isNotEmpty == true)
Text(
'@$uname',
maxLines: 1,
overflow: .ellipsis,
style: TextStyle(
color: theme
.colorScheme
.primary,
),
),
Text(
'识别二维码,$viewType$itemType',
textAlign: .end,
style: TextStyle(
color: theme
.colorScheme
.onSurfaceVariant,
),
),
Text(
DateFormatUtils.longFormatDs
.format(.now()),
textAlign: .end,
style: TextStyle(
fontSize: 13,
color:
theme.colorScheme.outline,
),
),
],
),
),
GestureDetector(
onTap: () => Utils.copyText(uri),
child: Container(
width: 88,
height: 88,
margin: const .all(12),
padding: const .all(3),
color: theme.brightness.isDark
? Colors.white
: theme.colorScheme.surface,
child: PrettyQrView.data(
data: uri,
decoration:
const PrettyQrDecoration(
shape:
PrettyQrSquaresSymbol(),
),
),
),
],
),
],
),
),
],
),
),
showBottom
? Stack(
clipBehavior: Clip.none,
children: [
if (uri.isNotEmpty)
Align(
alignment: Alignment.centerRight,
child: Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.end,
spacing: 4,
children: [
if (uname?.isNotEmpty == true)
Text(
'@$uname',
maxLines: 1,
overflow: .ellipsis,
style: TextStyle(
color: theme
.colorScheme
.primary,
),
),
Text(
'识别二维码,$viewType$itemType',
textAlign: TextAlign.end,
style: TextStyle(
color: theme
.colorScheme
.onSurfaceVariant,
),
),
Text(
DateFormatUtils.longFormatDs
.format(.now()),
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 13,
color: theme
.colorScheme
.outline,
),
),
],
),
),
GestureDetector(
onTap: () => Utils.copyText(uri),
child: Container(
width: 88,
height: 88,
margin: const .all(12),
padding: const .all(3),
color: theme.brightness.isDark
? Colors.white
: theme.colorScheme.surface,
child: PrettyQrView.data(
data: uri,
decoration:
const PrettyQrDecoration(
shape:
PrettyQrSquaresSymbol(),
),
),
),
),
],
),
),
Align(
alignment: Alignment.centerLeft,
child: Image.asset(
'assets/images/logo/logo_2.png',
width: 100,
cacheWidth: 100.cacheSize(context),
color:
theme.colorScheme.onSurfaceVariant,
),
),
],
)
: const SizedBox(height: 12),
],
),
),
Align(
alignment: .centerLeft,
child: Image.asset(
'assets/images/logo/logo_2.png',
width: 100,
cacheWidth: 100.cacheSize(context),
color: theme.colorScheme.onSurfaceVariant,
),
),
],
)
: const SizedBox(height: 12),
],
),
),
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black54,
],
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: .topCenter,
end: .bottomCenter,
colors: [
Colors.transparent,
Colors.black54,
],
),
child: Padding(
padding: EdgeInsets.only(
left: padding.left,
right: padding.right,
bottom: 25 + padding.bottom,
),
child: Row(
spacing: 40,
mainAxisAlignment: MainAxisAlignment.center,
children: [
),
child: Padding(
padding: EdgeInsets.only(
left: padding.left,
right: padding.right,
bottom: 25 + padding.bottom,
),
child: Row(
spacing: 40,
mainAxisAlignment: .center,
children: [
iconButton(
size: 42,
tooltip: '关闭',
icon: const Icon(Icons.clear),
onPressed: Get.back,
bgColor: theme.colorScheme.onInverseSurface,
iconColor: theme.colorScheme.onSurfaceVariant,
),
iconButton(
size: 42,
tooltip: showBottom ? '隐藏' : '显示',
context: context,
icon: showBottom
? const Icon(Icons.visibility_off)
: const Icon(Icons.visibility),
onPressed: () => setState(() {
showBottom = !showBottom;
}),
),
if (PlatformUtils.isMobile)
iconButton(
size: 42,
tooltip: '关闭',
icon: const Icon(Icons.clear),
onPressed: Get.back,
bgColor: theme.colorScheme.onInverseSurface,
iconColor: theme.colorScheme.onSurfaceVariant,
),
iconButton(
size: 42,
tooltip: showBottom ? '隐藏' : '显示',
tooltip: '分享',
context: context,
icon: showBottom
? const Icon(Icons.visibility_off)
: const Icon(Icons.visibility),
onPressed: () => setState(() {
showBottom = !showBottom;
}),
icon: const Icon(Icons.share),
onPressed: () => _onSaveOrSharePic(true),
),
if (PlatformUtils.isMobile)
iconButton(
size: 42,
tooltip: '分享',
context: context,
icon: const Icon(Icons.share),
onPressed: () => _onSaveOrSharePic(true),
),
iconButton(
size: 42,
tooltip: '保存',
context: context,
icon: const Icon(Icons.save_alt),
onPressed: _onSaveOrSharePic,
),
],
),
iconButton(
size: 42,
tooltip: '保存',
context: context,
icon: const Icon(Icons.save_alt),
onPressed: _onSaveOrSharePic,
),
],
),
),
),
],
),
),
],
);
}
}

View File

@@ -1,11 +1,11 @@
import 'dart:convert';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
import 'package:PiliPlus/common/widgets/disabled_icon.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/sliver_wrap.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/search/search_rcmd/data.dart';
import 'package:PiliPlus/pages/about/view.dart' show showImportExportDialog;
import 'package:PiliPlus/pages/search/controller.dart';
import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
@@ -424,12 +424,12 @@ class _SearchPageState extends State<SearchPage> {
onPressed: () => showImportExportDialog<List>(
context,
title: '历史记录',
toJson: () => jsonEncode(_searchController.historyList),
fromJson: (json) {
localFileName: () => 'search',
onExport: () => jsonEncode(_searchController.historyList),
onImport: (json) {
final list = List<String>.from(json);
_searchController.historyList.value = list;
GStorage.historyWord.put('cacheList', list);
return true;
},
),
);

View File

@@ -663,7 +663,9 @@ class VideoDetailController extends GetxController
(isFileSource
? true
: videoPlayerKey.currentState?.mounted == true)) {
return playerInit(autoFullScreenFlag: autoFullScreenFlag);
return playerInit(
autoFullScreenFlag: autoFullScreenFlag && _autoPlay.value,
);
}
return null;
}
@@ -687,6 +689,7 @@ class VideoDetailController extends GetxController
dir: args['dirPath'],
typeTag: entry.typeTag!,
isMp4: entry.mediaType == 1,
hasDashAudio: entry.hasDashAudio,
)
: NetworkSource(
videoSource: video ?? videoUrl!,

View File

@@ -309,13 +309,17 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
return null;
}
}
plPlayerController = videoDetailController.plPlayerController;
final plPlayerController = this.plPlayerController =
videoDetailController.plPlayerController;
videoDetailController.autoPlay = true;
plPlayerController!
plPlayerController
..addStatusLister(playerListener)
..addPositionListener(positionListener);
if (videoDetailController.plPlayerController.preInitPlayer) {
return plPlayerController!.play();
if (plPlayerController.preInitPlayer) {
if (plPlayerController.autoEnterFullScreen) {
plPlayerController.triggerFullScreen();
}
return plPlayerController.play();
} else {
return videoDetailController.playerInit(
autoplay: true,

View File

@@ -3,11 +3,11 @@ import 'dart:convert';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:webdav_client/webdav_client.dart' as webdav;
class WebDav {
@@ -53,12 +53,7 @@ class WebDav {
}
String _getFileName() {
final type = PlatformUtils.isDesktop
? 'desktop'
: Get.context!.isTablet
? 'pad'
: 'phone';
return 'piliplus_settings_$type.json';
return 'piliplus_settings_${Get.context!.platformName}.json';
}
Future<void> backup() async {

View File

@@ -34,7 +34,6 @@ import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/asset_utils.dart';
import 'package:PiliPlus/utils/extension/box_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
@@ -369,7 +368,7 @@ class PlPlayerController with BlockConfigMixin {
late final showFsLockBtn = Pref.showFsLockBtn;
late final keyboardControl = Pref.keyboardControl;
late final bool _autoEnterFullScreen = Pref.autoEnterFullScreen;
late final bool autoEnterFullScreen = Pref.autoEnterFullScreen;
late final bool autoExitFullscreen = Pref.autoExitFullscreen;
late final bool autoPlayEnable = Pref.autoPlayEnable;
late final bool enableVerticalExpand = Pref.enableVerticalExpand;
@@ -630,7 +629,7 @@ class PlPlayerController with BlockConfigMixin {
// 数据加载完成
dataStatus.value = DataStatus.loaded;
if (autoFullScreenFlag && _autoEnterFullScreen) {
if (autoFullScreenFlag && autoEnterFullScreen) {
triggerFullScreen(status: true);
}
@@ -774,10 +773,11 @@ class PlPlayerController with BlockConfigMixin {
return;
}
_videoPlayerController = player;
if (isAnim && superResolutionType.value != .disable) {
await setShader();
}
}
if (isAnim) await setShader();
final Map<String, String> extras = {};
String video = dataSource.videoSource;
@@ -788,33 +788,32 @@ class PlPlayerController with BlockConfigMixin {
extras['audio-files'] =
'"${Platform.isWindows ? audio.replaceAll(';', r'\;') : audio.replaceAll(':', r'\:')}"';
}
}
if (kDebugMode || Platform.isAndroid) {
String audioNormalization = AudioNormalization.getParamFromConfig(
Pref.audioNormalization,
);
if (volume != null && volume.isNotEmpty) {
audioNormalization = audioNormalization.replaceFirstMapped(
loudnormRegExp,
(i) =>
'loudnorm=${volume.format(
Map.fromEntries(
i.group(1)!.split(':').map((item) {
final parts = item.split('=');
return MapEntry(parts[0].toLowerCase(), num.parse(parts[1]));
}),
),
)}',
if (kDebugMode || Platform.isAndroid) {
String audioNormalization = AudioNormalization.getParamFromConfig(
Pref.audioNormalization,
);
} else {
audioNormalization = audioNormalization.replaceFirst(
loudnormRegExp,
AudioNormalization.getParamFromConfig(Pref.fallbackNormalization),
);
}
if (audioNormalization.isNotEmpty) {
extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"';
if (volume != null && volume.isNotEmpty) {
audioNormalization = audioNormalization.replaceFirstMapped(
loudnormRegExp,
(i) =>
'loudnorm=${volume.format(
Map.fromEntries(
i.group(1)!.split(':').map((item) {
final parts = item.split('=');
return MapEntry(parts[0].toLowerCase(), num.parse(parts[1]));
}),
),
)}',
);
} else {
audioNormalization = audioNormalization.replaceFirst(
loudnormRegExp,
AudioNormalization.getParamFromConfig(Pref.fallbackNormalization),
);
}
if (audioNormalization.isNotEmpty) {
extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"';
}
}
}
@@ -828,38 +827,17 @@ class PlPlayerController with BlockConfigMixin {
);
}
Future<bool> refreshPlayer() async {
Future<void>? refreshPlayer() {
if (dataSource is FileSource) {
return true;
return null;
}
if (_videoPlayerController == null) {
// SmartDialog.showToast('视频播放器为空,请重新进入本页面');
return false;
if (_videoPlayerController?.current.isNotEmpty ?? false) {
return _videoPlayerController!.open(
_videoPlayerController!.current.last.copyWith(start: position),
play: true,
);
}
if (dataSource.videoSource.isNullOrEmpty) {
SmartDialog.showToast('视频源为空,请重新进入本页面');
return false;
}
String? audioUri;
if (!isLive) {
if (dataSource.audioSource.isNullOrEmpty) {
SmartDialog.showToast('音频源为空');
} else {
audioUri = Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:');
}
}
await _videoPlayerController!.open(
Media(
dataSource.videoSource,
start: position,
extras: audioUri == null ? null : {'audio-files': '"$audioUri"'},
),
play: true,
);
return true;
// seekTo(currentPos);
return null;
}
// 开始播放
@@ -1000,7 +978,7 @@ class PlPlayerController with BlockConfigMixin {
'controllerStream.error.listen',
const Duration(milliseconds: 10000),
() {
Future.delayed(const Duration(milliseconds: 3000), () async {
Future.delayed(const Duration(milliseconds: 3000), () {
// if (kDebugMode) {
// debugPrint("isBuffering.value: ${isBuffering.value}");
// }
@@ -1012,9 +990,7 @@ class PlPlayerController with BlockConfigMixin {
'视频链接打开失败,重试中',
displayTime: const Duration(milliseconds: 500),
);
if (!await refreshPlayer()) {
if (kDebugMode) debugPrint("failed");
}
refreshPlayer();
}
});
},

View File

@@ -25,6 +25,7 @@ class FileSource extends DataSource {
FileSource({
required this.dir,
required this.isMp4,
required bool hasDashAudio,
required String typeTag,
}) : super(
videoSource: path.join(
@@ -32,7 +33,7 @@ class FileSource extends DataSource {
typeTag,
isMp4 ? PathUtils.videoNameType1 : PathUtils.videoNameType2,
),
audioSource: isMp4
audioSource: isMp4 || !hasDashAudio
? null
: path.join(dir, typeTag, PathUtils.audioNameType2),
);

View File

@@ -138,7 +138,7 @@ class DownloadService extends GetxService {
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final entry = BiliDownloadEntryInfo(
mediaType: 2,
hasDashAudio: true,
hasDashAudio: false,
isCompleted: false,
totalBytes: 0,
downloadedBytes: 0,
@@ -206,7 +206,7 @@ class DownloadService extends GetxService {
);
final entry = BiliDownloadEntryInfo(
mediaType: 2,
hasDashAudio: true,
hasDashAudio: false,
isCompleted: false,
totalBytes: 0,
downloadedBytes: 0,

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart';
/// from Getx
@@ -71,4 +72,10 @@ 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

@@ -221,6 +221,10 @@ abstract final class PageUtils {
},
);
} else {
if (item.linkFolded) {
pushDynFromId(id: item.idStr);
return;
}
toDupNamed(
'/dynamicDetail',
arguments: {

View File

@@ -87,12 +87,13 @@ abstract final class GStorage {
static Future<void> importAllSettings(String data) =>
importAllJsonSettings(jsonDecode(data));
static Future<bool> importAllJsonSettings(Map<String, dynamic> map) async {
await Future.wait([
static Future<List<void>> importAllJsonSettings(
Map<String, dynamic> map,
) {
return Future.wait([
setting.clear().then((_) => setting.putAll(map[setting.name])),
video.clear().then((_) => video.putAll(map[video.name])),
]);
return true;
}
static void regAdapter() {
@@ -107,8 +108,8 @@ abstract final class GStorage {
..registerAdapter(RuleFilterAdapter());
}
static Future<void> compact() async {
await Future.wait([
static Future<List<void>> compact() {
return Future.wait([
userInfo.compact(),
historyWord.compact(),
localCache.compact(),
@@ -120,8 +121,8 @@ abstract final class GStorage {
]);
}
static Future<void> close() async {
await Future.wait([
static Future<List<void>> close() {
return Future.wait([
userInfo.close(),
historyWord.close(),
localCache.close(),
@@ -133,6 +134,19 @@ abstract final class GStorage {
]);
}
static Future<List<void>> clear() {
return Future.wait([
userInfo.clear(),
historyWord.clear(),
localCache.clear(),
setting.clear(),
video.clear(),
Accounts.clear(),
watchProgress.clear(),
?reply?.clear(),
]);
}
static int _intStrKeyComparator(dynamic k1, dynamic k2) {
if (k1 is int) {
if (k2 is int) {

View File

@@ -578,10 +578,10 @@ packages:
dependency: "direct main"
description:
name: fl_chart
sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9"
sha256: b938f77d042cbcd822936a7a359a7235bad8bd72070de1f827efc2cc297ac888
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.2.0"
flex_seed_scheme:
dependency: "direct main"
description:
@@ -742,10 +742,10 @@ packages:
description:
path: "."
ref: main
resolved-ref: c08f651c60f1451feba2ec4895caf12771661809
resolved-ref: b87bda5672e1c8494853bb44bbf08515ef748bca
url: "https://github.com/bggRGjQaUbCoE/flutter_smart_dialog.git"
source: git
version: "5.0.1"
version: "5.1.0"
flutter_sortable_wrap:
dependency: "direct main"
description:
@@ -1943,10 +1943,10 @@ packages:
dependency: "direct main"
description:
name: wakelock_plus
sha256: e4e125b7c1a2f0e491e5452afdc0e25ab77b2d2775a7caa231fcc1c1f2162c47
sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "1.5.1"
wakelock_plus_platform_interface:
dependency: transitive
description:

View File

@@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
# update when release
version: 2.0.0+1
version: 2.0.1+1
environment:
sdk: ">=3.10.0"