From d6e6e52df2266d150f8d83e1d55572a8335a138b Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:31:26 +0800 Subject: [PATCH] tweaks (#1816) * opt: getFileName * opt: audio-pitch-correction * opt: spring dialog * opt: account dialog * update [skip ci] --------- Co-authored-by: dom --- lib/pages/login/controller.dart | 200 +++++++++---------- lib/pages/setting/models/style_settings.dart | 121 +++++------ lib/plugin/pl_player/controller.dart | 2 +- lib/utils/utils.dart | 29 ++- 4 files changed, 183 insertions(+), 169 deletions(-) diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index a2a1496d2..08f4dc5da 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -740,116 +740,114 @@ class LoginPageController extends GetxController bool quickSelect = selectAccount.every((e) => e == selectAccount.first); return showDialog( context: context, - builder: (context) => Builder( - builder: (context) => AlertDialog( - title: Row( - crossAxisAlignment: .start, - mainAxisAlignment: .spaceBetween, - children: [ - Text.rich( - style: const TextStyle(height: 1.5), - TextSpan( - children: [ - const TextSpan(text: '账号切换'), - TextSpan( - text: '\nmid 为0时使用匿名', - style: TextStyle( - fontSize: 14, - color: ColorScheme.of(context).outline, - ), + builder: (context) => AlertDialog( + title: Row( + crossAxisAlignment: .start, + mainAxisAlignment: .spaceBetween, + children: [ + Text.rich( + style: const TextStyle(height: 1.5), + TextSpan( + children: [ + const TextSpan(text: '账号切换'), + TextSpan( + text: '\nmid 为0时使用匿名', + style: TextStyle( + fontSize: 14, + color: ColorScheme.of(context).outline, ), - ], - ), - ), - TextButton( - onPressed: () { - quickSelect = !quickSelect; - (context as Element).markNeedsBuild(); - }, - child: const Text('切换'), - ), - ], - ), - titlePadding: const .only(left: 22, top: 16, right: 22, bottom: 3), - contentPadding: const .symmetric(vertical: 5), - actionsPadding: const .only(left: 16, right: 16, bottom: 10), - content: SingleChildScrollView( - child: AnimatedSize( - curve: Curves.easeIn, - alignment: .topCenter, - duration: const Duration(milliseconds: 200), - child: quickSelect - ? Builder( - builder: (context) => RadioGroup( - groupValue: selectAccount[0], - onChanged: (v) { - for (int i = 0; i < selectAccount.length; i++) { - selectAccount[i] = v!; - } - (context as Element).markNeedsBuild(); - }, - child: Column( - crossAxisAlignment: .start, - children: options.entries - .map( - (entry) => RadioWidget( - value: entry.key, - title: entry.value, - mainAxisSize: .max, - padding: const .only(left: 12), - ), - ) - .toList(), - ), - ), - ) - : Column( - crossAxisAlignment: .start, - children: AccountType.values - .map( - (e) => Builder( - builder: (context) => RadioGroup( - groupValue: selectAccount[e.index], - onChanged: (v) { - selectAccount[e.index] = v!; - (context as Element).markNeedsBuild(); - }, - child: WrapRadioOptionsGroup( - groupTitle: e.title, - options: options, - ), - ), - ), - ) - .toList(), - ), - ), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle(color: ColorScheme.of(context).outline), + ), + ], ), ), TextButton( onPressed: () { - Get.back(); - for (final type in AccountType.values) { - final index = type.index; - final account = quickSelect - ? selectAccount.first - : selectAccount[index]; - if (account != Accounts.accountMode[index]) { - Accounts.set(type, account); - } - } + quickSelect = !quickSelect; + (context as Element).markNeedsBuild(); }, - child: const Text('确定'), + child: const Text('切换'), ), ], ), + titlePadding: const .only(left: 22, top: 16, right: 22, bottom: 3), + contentPadding: const .symmetric(vertical: 5), + actionsPadding: const .only(left: 16, right: 16, bottom: 10), + content: SingleChildScrollView( + child: AnimatedSize( + curve: Curves.easeIn, + alignment: .topCenter, + duration: const Duration(milliseconds: 200), + child: quickSelect + ? Builder( + builder: (context) => RadioGroup( + groupValue: selectAccount[0], + onChanged: (v) { + selectAccount.fillRange(0, selectAccount.length, v); + (context as Element).markNeedsBuild(); + }, + child: Column( + crossAxisAlignment: .start, + children: options.entries + .map( + (entry) => RadioWidget( + value: entry.key, + title: entry.value, + mainAxisSize: .max, + padding: PlatformUtils.isDesktop + ? const .only(left: 12) + : const .only(left: 12, top: 2, bottom: 2), + ), + ) + .toList(), + ), + ), + ) + : Column( + crossAxisAlignment: .start, + children: AccountType.values + .map( + (e) => Builder( + builder: (context) => RadioGroup( + groupValue: selectAccount[e.index], + onChanged: (v) { + selectAccount[e.index] = v!; + (context as Element).markNeedsBuild(); + }, + child: WrapRadioOptionsGroup( + groupTitle: e.title, + options: options, + ), + ), + ), + ) + .toList(), + ), + ), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle(color: ColorScheme.of(context).outline), + ), + ), + TextButton( + onPressed: () { + Get.back(); + for (final type in AccountType.values) { + final index = type.index; + final account = quickSelect + ? selectAccount.first + : selectAccount[index]; + if (account != Accounts.accountMode[index]) { + Accounts.set(type, account); + } + } + }, + child: const Text('确定'), + ), + ], ), ); } diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index 0fd710ab5..71803bba9 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:math' as math; import 'package:PiliPlus/common/widgets/color_palette.dart'; import 'package:PiliPlus/common/widgets/custom_toast.dart'; @@ -490,60 +491,41 @@ void _showUiScaleDialog( ); } -void _showSpringDurationDialog(BuildContext context) { - String initialValue = '500'; - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('滑动时间'), - content: TextFormField( - autofocus: true, - keyboardType: .number, - initialValue: initialValue, - onChanged: (value) => initialValue = value, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - decoration: const InputDecoration(suffixText: 'ms'), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle(color: ColorScheme.of(context).outline), - ), - ), - TextButton( - onPressed: () { - try { - final milliseconds = int.parse(initialValue); - Get.back(); - final springDescription = SpringDescription.withDurationAndBounce( - duration: Duration(milliseconds: milliseconds), - ); - GStorage.setting.put( - SettingBoxKey.springDescription, - [ - springDescription.mass, - springDescription.stiffness, - springDescription.damping, - ], - ); - SmartDialog.showToast('设置成功,重启生效'); - } catch (e) { - SmartDialog.showToast(e.toString()); - } - }, - child: const Text('确定'), - ), - ], - ), - ); -} - void _showSpringDialog(BuildContext context, _) { final List springDescription = Pref.springDescription .map((i) => i.toString()) - .toList(); + .toList(growable: false); + bool physicalMode = true; + + void physical2Duration() { + final mass = double.parse(springDescription[0]); + final stiffness = double.parse(springDescription[1]); + final damping = double.parse(springDescription[2]); + + final duration = math.sqrt(4 * math.pi * math.pi * mass / stiffness); + final dampingRatio = damping / (2.0 * math.sqrt(mass * stiffness)); + final bounce = dampingRatio < 1.0 + ? 1.0 - dampingRatio + : 1.0 / dampingRatio - 1; + + springDescription[0] = duration.toString(); + springDescription[1] = bounce.toString(); + } + + /// from [SpringDescription.withDurationAndBounce] but with higher precision + void duration2Physical() { + final duration = double.parse(springDescription[0]); + final bounce = double.parse(springDescription[1]).clamp(-1.0, 1.0); + + final stiffness = 4 * math.pi * math.pi / math.pow(duration, 2); + final dampingRatio = bounce > 0 ? 1.0 - bounce : 1.0 / (bounce + 1); + final damping = 2 * math.sqrt(stiffness) * dampingRatio; + + springDescription[0] = '1'; + springDescription[1] = stiffness.toString(); + springDescription[2] = damping.toString(); + } + showDialog( context: context, builder: (context) => AlertDialog( @@ -557,27 +539,45 @@ void _showSpringDialog(BuildContext context, _) { tapTargetSize: .shrinkWrap, ), onPressed: () { - Get.back(); - _showSpringDurationDialog(context); + try { + if (physicalMode) { + physical2Duration(); + } else { + duration2Physical(); + } + physicalMode = !physicalMode; + (context as Element).markNeedsBuild(); + } catch (e) { + SmartDialog.showToast(e.toString()); + } }, - child: const Text('滑动时间'), + child: Text(physicalMode ? '滑动时间' : '物理参数'), ), ], ), content: Column( - mainAxisSize: MainAxisSize.min, + key: ValueKey(physicalMode), + mainAxisSize: .min, children: List.generate( - 3, + physicalMode ? 3 : 2, (index) => TextFormField( autofocus: index == 0, initialValue: springDescription[index], - keyboardType: const .numberWithOptions(decimal: true), + keyboardType: .numberWithOptions( + signed: !physicalMode && index == 1, + decimal: true, + ), onChanged: (value) => springDescription[index] = value, inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), + !physicalMode && index == 1 + ? FilteringTextInputFormatter.allow(RegExp(r'[-\d\.]+')) + : FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), ], decoration: InputDecoration( - labelText: const ['mass', 'stiffness', 'damping'][index], + labelText: (physicalMode + ? const ['mass', 'stiffness', 'damping'] + : const ['duration', 'bounce'])[index], + suffixText: !physicalMode && index == 0 ? 's' : null, ), ), ), @@ -601,6 +601,9 @@ void _showSpringDialog(BuildContext context, _) { TextButton( onPressed: () { try { + if (!physicalMode) { + duration2Physical(); + } final res = springDescription.map(double.parse).toList(); Get.back(); GStorage.setting.put(SettingBoxKey.springDescription, res); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index b1eec4987..a67f878be 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -793,7 +793,7 @@ class PlPlayerController { if (isAnim) { setShader(superResolutionType.value, pp); } - await pp.setProperty("af", "scaletempo2=max-speed=8"); + // await pp.setProperty('audio-pitch-correction', 'yes'); // default yes if (Platform.isAndroid) { await pp.setProperty("volume-max", "100"); await pp.setProperty("ao", Pref.audioOutput); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index c5eb10677..65e409306 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -162,15 +162,28 @@ abstract final class Utils { return randomBase64.substring(0, randomBase64.length - 2); } - static int _getExt(String uri) { - final i = uri.indexOf('?'); - return i == -1 ? uri.length : i; - } - static String getFileName(String uri, {bool fileExt = true}) { - final i0 = uri.lastIndexOf('/') + 1; - final i1 = fileExt ? _getExt(uri) : uri.lastIndexOf('.'); - return uri.substring(i0, i1); + int slash = -1; + int dot = -1; + int qMark = uri.length; + + loop: + for (int index = uri.length - 1; index >= 0; index--) { + switch (uri.codeUnitAt(index)) { + case 0x2F: // `/` + slash = index; + break loop; + case 0x2E: // `.` + if (dot == -1) dot = index; + break; + case 0x3F: // `?` + qMark = index; + if (dot > qMark) dot = -1; + break; + } + } + RangeError.checkNotNegative(slash, '/'); + return uri.substring(slash + 1, (fileExt || dot == -1) ? qMark : dot); } /// When calling this from a `catch` block consider annotating the method