mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 08:38:18 +08:00
121
lib/common/widgets/scale_app.dart
Normal file
121
lib/common/widgets/scale_app.dart
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import 'dart:async' show scheduleMicrotask;
|
||||||
|
import 'dart:collection' show Queue;
|
||||||
|
import 'dart:ui' show PointerDataPacket;
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart' show PointerEventConverter;
|
||||||
|
import 'package:flutter/rendering.dart' show RenderView, ViewConfiguration;
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
/// ref https://github.com/LastMonopoly/scaled_app
|
||||||
|
|
||||||
|
/// Adapted from [WidgetsFlutterBinding]
|
||||||
|
///
|
||||||
|
class ScaledWidgetsFlutterBinding extends WidgetsFlutterBinding {
|
||||||
|
ScaledWidgetsFlutterBinding._({double scaleFactor = 1.0})
|
||||||
|
: _scaleFactor = scaleFactor;
|
||||||
|
|
||||||
|
/// Calculate scale factor from device size.
|
||||||
|
double _scaleFactor;
|
||||||
|
|
||||||
|
/// Update scaleFactor callback, then rebuild layout
|
||||||
|
void setScaleFactor(double scaleFactor) {
|
||||||
|
if (_scaleFactor == scaleFactor) return;
|
||||||
|
_scaleFactor = scaleFactor;
|
||||||
|
handleMetricsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
double devicePixelRatioScaled = 0;
|
||||||
|
|
||||||
|
static ScaledWidgetsFlutterBinding? _binding;
|
||||||
|
|
||||||
|
static ScaledWidgetsFlutterBinding get instance => _binding!;
|
||||||
|
|
||||||
|
/// Scaling will be applied based on [scaleFactor] callback.
|
||||||
|
///
|
||||||
|
static WidgetsBinding ensureInitialized({double scaleFactor = 1.0}) =>
|
||||||
|
_binding ??= ScaledWidgetsFlutterBinding._(scaleFactor: scaleFactor);
|
||||||
|
|
||||||
|
/// Override the method from [RendererBinding.createViewConfiguration] to
|
||||||
|
/// change what size or device pixel ratio the [RenderView] will use.
|
||||||
|
///
|
||||||
|
/// See more:
|
||||||
|
/// * [RendererBinding.createViewConfiguration]
|
||||||
|
/// * [TestWidgetsFlutterBinding.createViewConfiguration]
|
||||||
|
@override
|
||||||
|
ViewConfiguration createViewConfigurationFor(RenderView renderView) {
|
||||||
|
final view = renderView.flutterView;
|
||||||
|
final devicePixelRatio = view.devicePixelRatio;
|
||||||
|
devicePixelRatioScaled = devicePixelRatio * _scaleFactor;
|
||||||
|
final BoxConstraints physicalConstraints =
|
||||||
|
BoxConstraints.fromViewConstraints(view.physicalConstraints);
|
||||||
|
return ViewConfiguration(
|
||||||
|
physicalConstraints: physicalConstraints,
|
||||||
|
logicalConstraints: physicalConstraints / devicePixelRatioScaled,
|
||||||
|
devicePixelRatio: devicePixelRatioScaled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adapted from [GestureBinding.initInstances]
|
||||||
|
@override
|
||||||
|
void initInstances() {
|
||||||
|
super.initInstances();
|
||||||
|
platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void unlocked() {
|
||||||
|
super.unlocked();
|
||||||
|
_flushPointerEventQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
|
||||||
|
|
||||||
|
/// When we scale UI using [ViewConfiguration], [ui.window] stays the same.
|
||||||
|
///
|
||||||
|
/// [GestureBinding] uses [platformDispatcher.implicitView.devicePixelRatio] for calculations,
|
||||||
|
/// so we override corresponding methods.
|
||||||
|
///
|
||||||
|
void _handlePointerDataPacket(PointerDataPacket packet) {
|
||||||
|
// We convert pointer data to logical pixels so that e.g. the touch slop can be
|
||||||
|
// defined in a device-independent manner.
|
||||||
|
try {
|
||||||
|
_pendingPointerEvents.addAll(
|
||||||
|
PointerEventConverter.expand(packet.data, _devicePixelRatioForView),
|
||||||
|
);
|
||||||
|
if (!locked) {
|
||||||
|
_flushPointerEventQueue();
|
||||||
|
}
|
||||||
|
} catch (error, stack) {
|
||||||
|
FlutterError.reportError(
|
||||||
|
FlutterErrorDetails(
|
||||||
|
exception: error,
|
||||||
|
stack: stack,
|
||||||
|
library: 'gestures library',
|
||||||
|
context: ErrorDescription('while handling a pointer data packet'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _devicePixelRatioForView(int viewId) => devicePixelRatioScaled;
|
||||||
|
|
||||||
|
/// Dispatch a [PointerCancelEvent] for the given pointer soon.
|
||||||
|
///
|
||||||
|
/// The pointer event will be dispatched before the next pointer event and
|
||||||
|
/// before the end of the microtask but not within this function call.
|
||||||
|
@override
|
||||||
|
void cancelPointer(int pointer) {
|
||||||
|
if (_pendingPointerEvents.isEmpty && !locked) {
|
||||||
|
scheduleMicrotask(_flushPointerEventQueue);
|
||||||
|
}
|
||||||
|
_pendingPointerEvents.addFirst(PointerCancelEvent(pointer: pointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _flushPointerEventQueue() {
|
||||||
|
assert(!locked);
|
||||||
|
|
||||||
|
while (_pendingPointerEvents.isNotEmpty) {
|
||||||
|
handlePointerEvent(_pendingPointerEvents.removeFirst());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/build_config.dart';
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||||
import 'package:PiliPlus/common/widgets/mouse_back.dart';
|
import 'package:PiliPlus/common/widgets/mouse_back.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/scale_app.dart';
|
||||||
import 'package:PiliPlus/http/init.dart';
|
import 'package:PiliPlus/http/init.dart';
|
||||||
import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
|
import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
@@ -46,7 +47,7 @@ import 'package:window_manager/window_manager.dart' hide calcWindowPosition;
|
|||||||
WebViewEnvironment? webViewEnvironment;
|
WebViewEnvironment? webViewEnvironment;
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
ScaledWidgetsFlutterBinding.ensureInitialized();
|
||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
tmpDirPath = (await getTemporaryDirectory()).path;
|
tmpDirPath = (await getTemporaryDirectory()).path;
|
||||||
appSupportDirPath = (await getApplicationSupportDirectory()).path;
|
appSupportDirPath = (await getApplicationSupportDirectory()).path;
|
||||||
@@ -57,6 +58,7 @@ void main() async {
|
|||||||
if (kDebugMode) debugPrint('GStorage init error: $e');
|
if (kDebugMode) debugPrint('GStorage init error: $e');
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
ScaledWidgetsFlutterBinding.instance.setScaleFactor(Pref.uiScale);
|
||||||
|
|
||||||
if (PlatformUtils.isDesktop) {
|
if (PlatformUtils.isDesktop) {
|
||||||
final customDownPath = Pref.downloadPath;
|
final customDownPath = Pref.downloadPath;
|
||||||
@@ -298,29 +300,16 @@ class MyApp extends StatelessWidget {
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final textScaler = TextScaler.linear(Pref.defaultTextScale);
|
final textScaler = TextScaler.linear(Pref.defaultTextScale);
|
||||||
if (uiScale != 1.0) {
|
if (uiScale != 1.0) {
|
||||||
// Apply full UI scaling for desktop
|
|
||||||
final actualSize = mediaQuery.size;
|
|
||||||
final scaledSize = actualSize / uiScale;
|
|
||||||
child = MediaQuery(
|
child = MediaQuery(
|
||||||
data: mediaQuery.copyWith(
|
data: mediaQuery.copyWith(
|
||||||
// Tell child the logical size it should layout to
|
|
||||||
size: scaledSize,
|
|
||||||
textScaler: textScaler,
|
textScaler: textScaler,
|
||||||
|
size: mediaQuery.size / uiScale,
|
||||||
padding: mediaQuery.padding / uiScale,
|
padding: mediaQuery.padding / uiScale,
|
||||||
viewPadding: mediaQuery.viewPadding / uiScale,
|
|
||||||
viewInsets: mediaQuery.viewInsets / uiScale,
|
viewInsets: mediaQuery.viewInsets / uiScale,
|
||||||
|
viewPadding: mediaQuery.viewPadding / uiScale,
|
||||||
|
devicePixelRatio: mediaQuery.devicePixelRatio * uiScale,
|
||||||
),
|
),
|
||||||
// Use OverflowBox to let child layout to scaledSize,
|
child: child!,
|
||||||
// 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 {
|
} else {
|
||||||
child = MediaQuery(
|
child = MediaQuery(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart';
|
|||||||
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
|
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@@ -295,11 +294,9 @@ class LiveRoomChatPanel extends StatelessWidget {
|
|||||||
TapUpDetails details,
|
TapUpDetails details,
|
||||||
DanmakuMsg item,
|
DanmakuMsg item,
|
||||||
) {
|
) {
|
||||||
final uiScale = Pref.uiScale;
|
final dx = details.globalPosition.dx;
|
||||||
final dx = details.globalPosition.dx / uiScale;
|
|
||||||
final renderBox = itemContext.findRenderObject() as RenderBox;
|
final renderBox = itemContext.findRenderObject() as RenderBox;
|
||||||
final dy =
|
final dy = renderBox.localToGlobal(renderBox.size.bottomLeft(.zero)).dy;
|
||||||
renderBox.localToGlobal(renderBox.size.bottomLeft(.zero)).dy / uiScale;
|
|
||||||
final autoScroll =
|
final autoScroll =
|
||||||
liveRoomController.autoScroll &&
|
liveRoomController.autoScroll &&
|
||||||
!liveRoomController.disableAutoScroll.value;
|
!liveRoomController.disableAutoScroll.value;
|
||||||
|
|||||||
@@ -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/custom_toast.dart';
|
||||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/scale_app.dart';
|
||||||
import 'package:PiliPlus/common/widgets/stateful_builder.dart';
|
import 'package:PiliPlus/common/widgets/stateful_builder.dart';
|
||||||
import 'package:PiliPlus/main.dart';
|
import 'package:PiliPlus/main.dart';
|
||||||
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
|
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
|
||||||
@@ -56,12 +57,6 @@ List<SettingsModel> get styleSettings => [
|
|||||||
needReboot: true,
|
needReboot: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
NormalModel(
|
|
||||||
title: '界面缩放',
|
|
||||||
getSubtitle: () => '当前缩放比例:${Pref.uiScale.toStringAsFixed(2)}',
|
|
||||||
leading: const Icon(Icons.zoom_in_outlined),
|
|
||||||
onTap: _showUiScaleDialog,
|
|
||||||
),
|
|
||||||
SwitchModel(
|
SwitchModel(
|
||||||
title: '横屏适配',
|
title: '横屏适配',
|
||||||
subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】',
|
subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】',
|
||||||
@@ -116,6 +111,12 @@ List<SettingsModel> get styleSettings => [
|
|||||||
Get.forceAppUpdate();
|
Get.forceAppUpdate();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
NormalModel(
|
||||||
|
title: '界面缩放',
|
||||||
|
getSubtitle: () => '当前缩放比例:${Pref.uiScale.toStringAsFixed(2)}',
|
||||||
|
leading: const Icon(Icons.zoom_in_outlined),
|
||||||
|
onTap: _showUiScaleDialog,
|
||||||
|
),
|
||||||
NormalModel(
|
NormalModel(
|
||||||
title: '页面过渡动画',
|
title: '页面过渡动画',
|
||||||
leading: const Icon(Icons.animation),
|
leading: const Icon(Icons.animation),
|
||||||
@@ -846,10 +847,10 @@ void _showUiScaleDialog(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Pref.uiScale = 1.0;
|
|
||||||
GStorage.setting.delete(SettingBoxKey.uiScale).whenComplete(() {
|
GStorage.setting.delete(SettingBoxKey.uiScale).whenComplete(() {
|
||||||
setState();
|
setState();
|
||||||
Get.appUpdate();
|
Get.appUpdate();
|
||||||
|
ScaledWidgetsFlutterBinding.instance.setScaleFactor(1.0);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: const Text('重置'),
|
child: const Text('重置'),
|
||||||
@@ -866,11 +867,11 @@ void _showUiScaleDialog(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Pref.uiScale = uiScale;
|
|
||||||
GStorage.setting.put(SettingBoxKey.uiScale, uiScale).whenComplete(
|
GStorage.setting.put(SettingBoxKey.uiScale, uiScale).whenComplete(
|
||||||
() {
|
() {
|
||||||
setState();
|
setState();
|
||||||
Get.appUpdate();
|
Get.appUpdate();
|
||||||
|
ScaledWidgetsFlutterBinding.instance.setScaleFactor(uiScale);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,10 +44,7 @@ abstract final class PageUtils {
|
|||||||
RouteObserver<PageRoute>();
|
RouteObserver<PageRoute>();
|
||||||
|
|
||||||
static RelativeRect menuPosition(Offset offset) {
|
static RelativeRect menuPosition(Offset offset) {
|
||||||
final uiScale = Pref.uiScale;
|
return .fromLTRB(offset.dx, offset.dy, offset.dx, 0);
|
||||||
final dx = offset.dx / uiScale;
|
|
||||||
final dy = offset.dy / uiScale;
|
|
||||||
return .fromLTRB(dx, dy, dx, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> imageView({
|
static Future<void> imageView({
|
||||||
|
|||||||
@@ -649,10 +649,8 @@ abstract final class Pref {
|
|||||||
static double get defaultTextScale =>
|
static double get defaultTextScale =>
|
||||||
_setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
_setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||||
|
|
||||||
static double uiScale = _setting.get(
|
static double get uiScale =>
|
||||||
SettingBoxKey.uiScale,
|
_setting.get(SettingBoxKey.uiScale, defaultValue: 1.0);
|
||||||
defaultValue: 1.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
static bool get dynamicsWaterfallFlow =>
|
static bool get dynamicsWaterfallFlow =>
|
||||||
_setting.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
_setting.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||||
|
|||||||
Reference in New Issue
Block a user