mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
feat: Add desktop scaling and fix linux postinst (#1800)
* fix: resolve Linux window close handler to prevent app hang - Add delete-event callback that properly quits the application when window is closed * feat: Add desktop scaling and fix linux postinst - Implement desktop interface scaling in main.dart using FittedBox. - Add desktop scaling setting UI. - Add desktopScale to storage preference. - Fix typos and logic in Linux postinst script. - Update piliplus.desktop with StartupWMClass. * update Signed-off-by: dom <githubaccount56556@proton.me> --------- Signed-off-by: Shao Guohao <shao.gh.98@gmail.com> Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -3,18 +3,18 @@
|
||||
ln -sf /opt/PiliPlus/piliplus /usr/bin/piliplus
|
||||
chmod +x /usr/bin/piliplus
|
||||
|
||||
if [ $1 == "config" ] && [ -x /usr/binupdate-mime-database ]; then
|
||||
if [ $1 == "configure" ] && [ -x /usr/bin/update-mime-database ]; then
|
||||
echo "updating mime database..."
|
||||
update-mime-database /usr/share/mime || true
|
||||
fi
|
||||
|
||||
if [ $1 == "config" ] && [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||
if [ $1 == "configure" ] && [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||
echo "updating icon cache..."
|
||||
gtk-update-icon-cache -q -f -t /usr/share/icons/hicolor || true
|
||||
fi
|
||||
|
||||
if [ $1 == "config" ] && [ -x /usr/bin/update-desktop-database ]; then
|
||||
echo "updating desktop database..."
|
||||
if [ $1 == "configure" ] && [ -x /usr/bin/update-desktop-database ]; then
|
||||
echo "configure desktop database..."
|
||||
update-desktop-database -q /usr/share/applications || true
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ Comment[zh_CN]=使用 Flutter 开发的 BiliBili 第三方客户端
|
||||
Exec=piliplus
|
||||
Icon=piliplus
|
||||
Terminal=false
|
||||
StartupWMClass=com.example.piliplus
|
||||
Categories=Video;AudioVideo;Player;
|
||||
|
||||
36
lib/common/widgets/stateful_builder.dart
Normal file
36
lib/common/widgets/stateful_builder.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StatefulBuilder extends StatefulWidget {
|
||||
const StatefulBuilder({
|
||||
super.key,
|
||||
this.onInit,
|
||||
this.onDispose,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
final VoidCallback? onInit;
|
||||
|
||||
final VoidCallback? onDispose;
|
||||
|
||||
final StatefulWidgetBuilder builder;
|
||||
|
||||
@override
|
||||
State<StatefulBuilder> createState() => _StatefulBuilderState();
|
||||
}
|
||||
|
||||
class _StatefulBuilderState extends State<StatefulBuilder> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.onInit?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.onDispose?.call();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.builder(context, setState);
|
||||
}
|
||||
@@ -292,15 +292,43 @@ class MyApp extends StatelessWidget {
|
||||
getPages: Routes.getPages,
|
||||
defaultTransition: Pref.pageTransition,
|
||||
builder: FlutterSmartDialog.init(
|
||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||
toastBuilder: (msg) => CustomToast(msg: msg),
|
||||
loadingBuilder: (msg) => LoadingWidget(msg: msg),
|
||||
builder: (context, child) {
|
||||
child = MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(Pref.defaultTextScale),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
final uiScale = Pref.uiScale;
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final textScaler = TextScaler.linear(Pref.defaultTextScale);
|
||||
if (uiScale != 1.0) {
|
||||
// Apply full UI scaling for desktop
|
||||
final actualSize = mediaQuery.size;
|
||||
final scaledSize = actualSize / uiScale;
|
||||
child = MediaQuery(
|
||||
data: mediaQuery.copyWith(
|
||||
// Tell child the logical size it should layout to
|
||||
size: scaledSize,
|
||||
padding: mediaQuery.padding / uiScale,
|
||||
viewPadding: mediaQuery.viewPadding / uiScale,
|
||||
viewInsets: mediaQuery.viewInsets / uiScale,
|
||||
textScaler: textScaler,
|
||||
),
|
||||
// Use OverflowBox to let child layout to scaledSize,
|
||||
// then FittedBox scales it to fit actualSize
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fill,
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
width: scaledSize.width,
|
||||
height: scaledSize.height,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
child = MediaQuery(
|
||||
data: mediaQuery.copyWith(textScaler: textScaler),
|
||||
child: child!,
|
||||
);
|
||||
}
|
||||
if (PlatformUtils.isDesktop) {
|
||||
return Focus(
|
||||
canRequestFocus: false,
|
||||
|
||||
@@ -34,7 +34,7 @@ class NormalModel extends SettingsModel {
|
||||
final ValueGetter<String>? getTitle;
|
||||
final ValueGetter<String>? getSubtitle;
|
||||
final Widget Function()? getTrailing;
|
||||
final void Function(BuildContext context, void Function() setState)? onTap;
|
||||
final void Function(BuildContext context, VoidCallback setState)? onTap;
|
||||
|
||||
const NormalModel({
|
||||
super.subtitle,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/color_palette.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/stateful_builder.dart';
|
||||
import 'package:PiliPlus/main.dart';
|
||||
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
|
||||
import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart';
|
||||
@@ -33,7 +34,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:auto_orientation/auto_orientation.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide StatefulBuilder;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -56,6 +57,12 @@ List<SettingsModel> get styleSettings => [
|
||||
needReboot: true,
|
||||
),
|
||||
],
|
||||
NormalModel(
|
||||
title: '界面缩放',
|
||||
getSubtitle: () => '当前缩放比例:${Pref.uiScale.toStringAsFixed(2)}',
|
||||
leading: const Icon(Icons.zoom_in_outlined),
|
||||
onTap: _showUiScaleDialog,
|
||||
),
|
||||
SwitchModel(
|
||||
title: '横屏适配',
|
||||
subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】',
|
||||
@@ -774,3 +781,107 @@ void _showQualityDialog({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const _minUiScale = 0.5;
|
||||
const _maxUiScale = 2.0;
|
||||
|
||||
void _showUiScaleDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) {
|
||||
double uiScale = Pref.uiScale;
|
||||
final textController = TextEditingController(
|
||||
text: uiScale.toStringAsFixed(2),
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('界面缩放'),
|
||||
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 12),
|
||||
content: StatefulBuilder(
|
||||
onDispose: textController.dispose,
|
||||
builder: (context, setDialogState) {
|
||||
return Column(
|
||||
spacing: 20,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Slider(
|
||||
padding: .zero,
|
||||
value: uiScale,
|
||||
min: _minUiScale,
|
||||
max: _maxUiScale,
|
||||
divisions: ((_maxUiScale - _minUiScale) * 20).toInt(),
|
||||
label: textController.text,
|
||||
onChanged: (value) => setDialogState(() {
|
||||
uiScale = value.toPrecision(2);
|
||||
textController.text = uiScale.toStringAsFixed(2);
|
||||
}),
|
||||
),
|
||||
TextFormField(
|
||||
controller: textController,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
inputFormatters: [
|
||||
LengthLimitingTextInputFormatter(4),
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[\d.]+')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: '缩放比例',
|
||||
hintText: '0.50 - 2.00',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final parsed = double.tryParse(value);
|
||||
if (parsed != null &&
|
||||
parsed >= _minUiScale &&
|
||||
parsed <= _maxUiScale) {
|
||||
setDialogState(() {
|
||||
uiScale = parsed;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
GStorage.setting.delete(SettingBoxKey.uiScale).whenComplete(() {
|
||||
setState();
|
||||
Get.appUpdate();
|
||||
});
|
||||
},
|
||||
child: const Text('重置'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
GStorage.setting.put(SettingBoxKey.uiScale, uiScale).whenComplete(
|
||||
() {
|
||||
setState();
|
||||
Get.appUpdate();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class NormalItem extends StatefulWidget {
|
||||
final ValueGetter<String>? getSubtitle;
|
||||
final Widget? leading;
|
||||
final ValueGetter<Widget?>? getTrailing;
|
||||
final void Function(BuildContext context, void Function() setState)? onTap;
|
||||
final void Function(BuildContext context, VoidCallback setState)? onTap;
|
||||
final EdgeInsetsGeometry? contentPadding;
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
|
||||
@@ -152,7 +152,8 @@ abstract final class SettingBoxKey {
|
||||
isWindowMaximized = 'isWindowMaximized',
|
||||
showWindowTitleBar = 'showWindowTitleBar',
|
||||
desktopVolume = 'desktopVolume',
|
||||
showTrayIcon = 'showTrayIcon';
|
||||
showTrayIcon = 'showTrayIcon',
|
||||
uiScale = 'uiScale';
|
||||
|
||||
static const String subtitlePreferenceV2 = 'subtitlePreferenceV2',
|
||||
enableDragSubtitle = 'enableDragSubtitle',
|
||||
|
||||
@@ -628,6 +628,9 @@ abstract final class Pref {
|
||||
static double get defaultTextScale =>
|
||||
_setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||
|
||||
static double get uiScale =>
|
||||
_setting.get(SettingBoxKey.uiScale, defaultValue: 1.0);
|
||||
|
||||
static bool get dynamicsWaterfallFlow =>
|
||||
_setting.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user