From c732438f8a9022a2f553b3724d3c2bd513148238 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 10 May 2024 10:09:25 +0800 Subject: [PATCH] feat: support scale up/down the app (#5300) --- .../lib/core/config/kv_keys.dart | 5 +++ frontend/appflowy_flutter/lib/main.dart | 4 +- .../appflowy_flutter/lib/startup/startup.dart | 7 ++-- .../lib/startup/tasks/app_widget.dart | 5 +-- .../tasks/app_window_size_manager.dart | 19 +++++++++ .../lib/startup/tasks/windows.dart | 23 ++++++++--- .../workspace/presentation/home/hotkeys.dart | 40 ++++++++++++++++++- frontend/appflowy_flutter/pubspec.lock | 8 ++++ frontend/appflowy_flutter/pubspec.yaml | 1 + 9 files changed, 95 insertions(+), 17 deletions(-) diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart index 748254d07d..ff97b61241 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart @@ -70,4 +70,9 @@ class KVKeys { /// The workspace id is a string. @Deprecated('deprecated in version 0.5.5') static const String lastOpenedWorkspaceId = 'lastOpenedWorkspaceId'; + + /// The key for saving the scale factor + /// + /// The value is a double string. + static const String scaleFactor = 'scaleFactor'; } diff --git a/frontend/appflowy_flutter/lib/main.dart b/frontend/appflowy_flutter/lib/main.dart index d55f2f81c5..9f140489c4 100644 --- a/frontend/appflowy_flutter/lib/main.dart +++ b/frontend/appflowy_flutter/lib/main.dart @@ -1,9 +1,9 @@ -import 'package:flutter/material.dart'; +import 'package:scaled_app/scaled_app.dart'; import 'startup/startup.dart'; Future main() async { - WidgetsFlutterBinding.ensureInitialized(); + ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.0); await runAppFlowy(); } diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart index d285b20dcd..3dac4f229c 100644 --- a/frontend/appflowy_flutter/lib/startup/startup.dart +++ b/frontend/appflowy_flutter/lib/startup/startup.dart @@ -1,13 +1,12 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/startup/tasks/feature_flag_task.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy_backend/appflowy_backend.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -120,7 +119,7 @@ class FlowyRunner { // localization const InitLocalizationTask(), // init the app window - const InitAppWindowTask(), + InitAppWindowTask(), // Init Rust SDK InitRustSDKTask(customApplicationPath: applicationDataDirectory), // Load Plugins, like document, grid ... diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index ad1fc542f5..9e719d6ecc 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/shared/feature_flags.dart'; @@ -24,6 +21,8 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart index 8a40051211..6c5bea392c 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart @@ -12,6 +12,9 @@ class WindowSizeManager { static const double maxWindowHeight = 8192.0; static const double maxWindowWidth = 8192.0; + static const double maxScaleFactor = 2.0; + static const double minScaleFactor = 0.5; + static const width = 'width'; static const height = 'height'; @@ -64,4 +67,20 @@ class WindowSizeManager { final offset = json.decode(position); return Offset(offset[dx], offset[dy]); } + + Future getScaleFactor() async { + final scaleFactor = await getIt().getWithFormat( + KVKeys.scaleFactor, + (value) => double.tryParse(value) ?? 1.0, + ) ?? + 1.0; + return scaleFactor.clamp(minScaleFactor, maxScaleFactor); + } + + Future setScaleFactor(double scaleFactor) async { + await getIt().set( + KVKeys.scaleFactor, + '${scaleFactor.clamp(minScaleFactor, maxScaleFactor)}', + ); + } } diff --git a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart index fa24a602b3..1238fab085 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ui'; import 'package:appflowy/core/helpers/helpers.dart'; @@ -5,15 +6,18 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/app_window_size_manager.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:scaled_app/scaled_app.dart'; import 'package:window_manager/window_manager.dart'; class InitAppWindowTask extends LaunchTask with WindowListener { - const InitAppWindowTask({ + InitAppWindowTask({ this.title = 'AppFlowy', }); final String title; + final windowsManager = WindowSizeManager(); + @override Future initialize(LaunchContext context) async { // Don't initialize on mobile or web. @@ -24,7 +28,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener { await windowManager.ensureInitialized(); windowManager.addListener(this); - final windowSize = await WindowSizeManager().getSize(); + final windowSize = await windowsManager.getSize(); final windowOptions = WindowOptions( size: windowSize, minimumSize: const Size( @@ -42,11 +46,18 @@ class InitAppWindowTask extends LaunchTask with WindowListener { await windowManager.show(); await windowManager.focus(); - final position = await WindowSizeManager().getPosition(); + final position = await windowsManager.getPosition(); if (position != null) { await windowManager.setPosition(position); } }); + + unawaited( + windowsManager.getScaleFactor().then( + (value) => + ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => value, + ), + ); } @override @@ -54,7 +65,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener { super.onWindowResize(); final currentWindowSize = await windowManager.getSize(); - return WindowSizeManager().setSize(currentWindowSize); + return windowsManager.setSize(currentWindowSize); } @override @@ -62,7 +73,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener { super.onWindowMaximize(); final currentWindowSize = await windowManager.getSize(); - return WindowSizeManager().setSize(currentWindowSize); + return windowsManager.setSize(currentWindowSize); } @override @@ -70,7 +81,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener { super.onWindowMoved(); final position = await windowManager.getPosition(); - return WindowSizeManager().setPosition(position); + return windowsManager.setPosition(position); } @override diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart index 6f120ced1d..ed5a667e91 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart @@ -1,16 +1,18 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/app_window_size_manager.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:flutter/material.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:provider/provider.dart'; +import 'package:scaled_app/scaled_app.dart'; typedef KeyDownHandler = void Function(HotKey hotKey); @@ -49,6 +51,8 @@ class HomeHotKeys extends StatefulWidget { } class _HomeHotKeysState extends State { + final windowSizeManager = WindowSizeManager(); + late final items = [ // Collapse sidebar menu HotKeyItem( @@ -118,6 +122,25 @@ class _HomeHotKeysState extends State { getIt().add(const RenameViewEvent.open()), ), + // Scale up/down the app + HotKeyItem( + hotKey: HotKey( + KeyCode.equal, + modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control], + scope: HotKeyScope.inapp, + ), + keyDownHandler: (_) => _scaleWithStep(0.1), + ), + + HotKeyItem( + hotKey: HotKey( + KeyCode.minus, + modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control], + scope: HotKeyScope.inapp, + ), + keyDownHandler: (_) => _scaleWithStep(-0.1), + ), + // Open settings dialog openSettingsHotKey(context, widget.userProfile), ]; @@ -149,4 +172,17 @@ class _HomeHotKeysState extends State { final bloc = context.read(); bloc.add(TabsEvent.selectTab(bloc.state.currentIndex + change)); } + + Future _scaleWithStep(double step) async { + final currentScaleFactor = await windowSizeManager.getScaleFactor(); + final textScale = (currentScaleFactor + step).clamp( + WindowSizeManager.minScaleFactor, + WindowSizeManager.maxScaleFactor, + ); + + Log.info('scale the app from $currentScaleFactor to $textScale'); + + ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => textScale; + await windowSizeManager.setScaleFactor(textScale); + } } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index a2c770dc1c..b46a113767 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -1521,6 +1521,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + scaled_app: + dependency: "direct main" + description: + name: scaled_app + sha256: "3415fad16d1cf283112988985ccd14c4cd28bf48cbe6432d59e158f3b632d58d" + url: "https://pub.dev" + source: hosted + version: "2.2.0" screen_retriever: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 6673e09dbe..76c16ec993 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -135,6 +135,7 @@ dependencies: numerus: ^2.1.2 flutter_animate: ^4.5.0 permission_handler: ^11.3.1 + scaled_app: ^2.2.0 dev_dependencies: flutter_lints: ^3.0.1