From a4ade4a7edee52505019401ce6df5326a765a5a0 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Tue, 9 Apr 2024 17:45:16 +0300 Subject: [PATCH] feat: font size updates + fixes --- lib/core/features/settings.dart | 7 ++ lib/core/store.dart | 15 +++- lib/main.dart | 31 ++++---- lib/pages/home_app_bar.dart | 23 ++++++ lib/pages/home_drawer.dart | 119 +++++++++++++++++++++++++++++ lib/pages/home_page.dart | 70 +++++++++-------- lib/pages/home_scaffold.dart | 129 ++------------------------------ lib/pages/settings_page.dart | 51 +++++++++++++ pubspec.lock | 16 ++-- pubspec.yaml | 7 +- 10 files changed, 288 insertions(+), 180 deletions(-) create mode 100644 lib/pages/home_app_bar.dart create mode 100644 lib/pages/home_drawer.dart diff --git a/lib/core/features/settings.dart b/lib/core/features/settings.dart index c6f9245..038f97f 100644 --- a/lib/core/features/settings.dart +++ b/lib/core/features/settings.dart @@ -42,34 +42,41 @@ class Settings { class GlobalSettings { bool keepAwake; double gameTextScale; + double uiTextScale; GlobalSettings({ required this.keepAwake, required this.gameTextScale, + required this.uiTextScale, }); factory GlobalSettings.empty() => GlobalSettings( keepAwake: true, gameTextScale: 1.0, + uiTextScale: 1.0, ); factory GlobalSettings.fromJson(Map json) => GlobalSettings( keepAwake: json['keepAwake'] as bool? ?? true, gameTextScale: json['gameTextScale'] as double? ?? 1.0, + uiTextScale: json['uiTextScale'] as double? ?? 1.0, ); Map toJson() => { 'keepAwake': keepAwake, 'gameTextScale': gameTextScale, + 'uiTextScale': uiTextScale, }; GlobalSettings copyWith({ bool? keepAwake, double? gameTextScale, + double? uiTextScale, }) { return GlobalSettings( keepAwake: keepAwake ?? this.keepAwake, gameTextScale: gameTextScale ?? this.gameTextScale, + uiTextScale: uiTextScale ?? this.uiTextScale, ); } } diff --git a/lib/core/store.dart b/lib/core/store.dart index c956d9b..e9dad34 100644 --- a/lib/core/store.dart +++ b/lib/core/store.dart @@ -66,8 +66,8 @@ class GameStore extends ChangeNotifier { Future init() async { debugPrint('GameStore.init'); await storage.init(); + await loadGlobalSettings(); debugPrint('storage.init $storage'); - loadGlobalSettings(); loadAllProfiles(); return this; } @@ -494,7 +494,7 @@ class GameStore extends ChangeNotifier { notifyListeners(); } - void loadGlobalSettings() async { + Future loadGlobalSettings() async { final settings = await storage.readFile('settings'); if (settings != null) { globalSettings = GlobalSettings.fromJson(settings); @@ -556,13 +556,21 @@ class GameStore extends ChangeNotifier { // TODO move to [GlobalSettings] void echoGlobalSettingsChanged(GlobalSettings old, GlobalSettings updated) { - echoSystem('Settings updated:'); + echoSystem('Global Settings updated:'); var updateCount = 0; if (updated.keepAwake != old.keepAwake) { updateCount++; echoSystem( 'Keep Screen Awake is now ${updated.keepAwake ? 'enabled' : 'disabled'}'); } + if (updated.uiTextScale != old.uiTextScale) { + updateCount++; + echoSystem('UI Text Scale is now ${updated.gameTextScale}'); + } + if (updated.gameTextScale != old.gameTextScale) { + updateCount++; + echoSystem('Game Output Text Scale is now ${updated.gameTextScale}'); + } if (updateCount == 0) { echoSystem(''); } @@ -654,6 +662,7 @@ class GameStore extends ChangeNotifier { Future saveGlobalSettings(GlobalSettings settings) async { globalSettings = settings; + notifyListeners(); storage.writeFile('settings', settings.toJson()); } } diff --git a/lib/main.dart b/lib/main.dart index dd46f37..b2f8648 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -52,23 +52,26 @@ class MudblockApp extends StatelessWidget { builder: (context, child) { return GameStore.provider( builder: (context, child) { - final store = GameStore.of(context); - return MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear(store.globalSettings.gameTextScale), + return GameStore.consumer( + builder: (context, store, child) => MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: + TextScaler.linear(store.globalSettings.uiTextScale), + ), + child: child!, + ), + child: Container( + color: darkTheme.colorScheme.background, + child: Padding( + padding: PlatformUtils.isDesktop + ? EdgeInsets.only(top: Platform.isMacOS ? 28.0 : 32.0) + : EdgeInsets.zero, + child: child, + ), ), - child: child!, ); }, - child: Container( - color: darkTheme.colorScheme.background, - child: Padding( - padding: PlatformUtils.isDesktop - ? EdgeInsets.only(top: Platform.isMacOS ? 28.0 : 32.0) - : EdgeInsets.zero, - child: child!, - ), - ), + child: child, ); }, initialRoute: Paths.home, diff --git a/lib/pages/home_app_bar.dart b/lib/pages/home_app_bar.dart new file mode 100644 index 0000000..27f7257 --- /dev/null +++ b/lib/pages/home_app_bar.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:mudblock/core/store.dart'; + +class HomeAppBar extends StatelessWidget implements PreferredSizeWidget { + const HomeAppBar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return AppBar( + title: GameStore.consumer( + builder: (context, store, child) => Text(store.connected + ? '${store.currentProfile.name} - Mudblock' + : 'Mudblock'), + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} + diff --git a/lib/pages/home_drawer.dart b/lib/pages/home_drawer.dart new file mode 100644 index 0000000..4a11c58 --- /dev/null +++ b/lib/pages/home_drawer.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:mudblock/core/features/alias.dart'; +import 'package:mudblock/core/features/game_button_set.dart'; +import 'package:mudblock/core/features/profile.dart'; +import 'package:mudblock/core/features/trigger.dart'; +import 'package:mudblock/core/features/variable.dart'; +import 'package:mudblock/core/platform_utils.dart'; +import 'package:mudblock/core/routes.dart'; +import 'package:mudblock/core/store.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class HomeDrawer extends StatelessWidget { + const HomeDrawer({super.key}); + + @override + Widget build(BuildContext context) { + final interaction = PlatformUtils.isDesktop ? 'Click' : 'Tap'; + return Drawer( + child: GameStore.consumer( + builder: (context, store, child) => ListView( + children: [ + ListTile( + leading: const Image( + image: AssetImage('assets/images/logo/logo.png'), + width: 32, + height: 32, + ), + title: const Text('Mudblock'), + subtitle: FutureBuilder( + future: PackageInfo.fromPlatform().then((pkg) => pkg.version), + builder: (context, data) { + final version = data.data ?? '...'; + return Text('v$version'); + }, + ), + onTap: () => Navigator.pushNamed(context, Paths.about), + ), + const Divider(), + ListTile( + title: store.connected + ? Text(store.currentProfile.name) + : const Text('Not connected'), + subtitle: store.connected + ? Text( + '${store.currentProfile.host}:${store.currentProfile.port}', + ) + : Text('$interaction to connect'), + leading: const CircleAvatar(child: Icon(Icons.cable)), + onTap: store.connected + ? () async { + final updated = await Navigator.pushNamed( + context, + Paths.profile, + arguments: store.currentProfile, + ); + if (updated != null) { + await (updated as MUDProfile).save(); + store.loadSavedProfiles(); + } + } + : () { + store.selectProfileAndConnect(context); + }, + ), + if (store.connected) ...[ + _tile(context, GameButtonSetData.iconData, 'Button Sets', + route: Paths.buttons), + _tile(context, Alias.iconData, 'Aliases', route: Paths.aliases), + _tile(context, Trigger.iconData, 'Triggers', + route: Paths.triggers), + _tile(context, Variable.iconData, 'Variables', + route: Paths.variables), + if (PlatformUtils.isDesktop) + _tile(context, Icons.keyboard, 'Keyboard Shortcuts', + route: Paths.shortcuts), + const Divider(), + _tile(context, Icons.cable, 'Profiles', route: Paths.profiles), + _tile(context, Icons.settings, 'Settings', route: Paths.settings), + _tile( + context, + Icons.exit_to_app, + 'Disconnect', + onTap: () async { + Navigator.of(context).pop(); + await gameStore.disconnect(); + if (context.mounted) { + gameStore.selectProfileAndConnect(context); + } + }, + ), + ], + if (!store.connected) + ListTile( + title: const Text('Settings'), + leading: const Icon(Icons.settings), + onTap: () => Navigator.pushNamed(context, Paths.settings), + ), + ], + ), + ), + ); + } + + Widget _tile( + BuildContext context, + IconData icon, + String title, { + String? route, + void Function()? onTap, + }) { + assert(route != null || onTap != null); + return ListTile( + title: Text(title), + leading: Icon(icon), + onTap: onTap ?? () => Navigator.pushNamed(context, route!), + ); + } +} + diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index fb3be19..130f394 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -76,41 +76,50 @@ class HomePageState extends State Expanded( child: Material( color: Colors.black, - child: Consumer( + child: GameStore.consumer( builder: (context, store, child) { final lines = store.lines; - return Theme( - data: Theme.of(context).copyWith( - textSelectionTheme: TextSelectionThemeData( - cursorColor: Colors.white, - selectionColor: Colors.white.withOpacity(0.3), - selectionHandleColor: Colors.white, + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear( + store.globalSettings.gameTextScale, ), ), - child: Stack( - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: store.selectInput, - child: SingleChildScrollView( - controller: store.scrollController, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Focus( - child: _buildGameOutput(lines), + child: Theme( + data: Theme.of(context).copyWith( + textSelectionTheme: TextSelectionThemeData( + cursorColor: Colors.white, + selectionColor: Colors.white.withOpacity(0.3), + selectionHandleColor: Colors.white, + ), + ), + child: Stack( + children: [ + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: store.selectInput, + child: SingleChildScrollView( + controller: store.scrollController, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Focus( + child: _buildGameOutput(lines), + ), + ), ), ), ), - ), - if (store.connected) - for (final buttonSet in store - .currentProfile.buttonSets - .where((b) => b.enabled)) - Padding( - padding: const EdgeInsets.all(8.0), - child: GameButtonSet(data: buttonSet), - ) - ], + if (store.connected) + for (final buttonSet in store + .currentProfile.buttonSets + .where((b) => b.enabled)) + Padding( + padding: const EdgeInsets.all(8.0), + child: GameButtonSet(data: buttonSet), + ) + ], + ), ), ); }, @@ -125,7 +134,8 @@ class HomePageState extends State controller: store.input, onSubmitted: store.submitAsInput, onTap: store.selectInput, - style: consoleStyle.copyWith(color: Theme.of(context).colorScheme.onSurface), + style: consoleStyle.copyWith( + color: Theme.of(context).colorScheme.onSurface), decoration: InputDecoration( hintText: 'Enter command', border: const OutlineInputBorder(), @@ -140,7 +150,7 @@ class HomePageState extends State ); } - SelectableText _buildGameOutput(List lines) { + Widget _buildGameOutput(List lines) { return SelectableText.rich( TextSpan( children: [ diff --git a/lib/pages/home_scaffold.dart b/lib/pages/home_scaffold.dart index 1e23ccb..fd7f3d7 100644 --- a/lib/pages/home_scaffold.dart +++ b/lib/pages/home_scaffold.dart @@ -1,15 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:mudblock/core/features/game_button_set.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; -import '../core/features/alias.dart'; -import '../core/features/profile.dart'; -import '../core/features/trigger.dart'; -import '../core/features/variable.dart'; -import '../core/platform_utils.dart'; -import '../core/routes.dart'; import '../core/store.dart'; +import 'home_app_bar.dart'; +import 'home_drawer.dart'; class HomeScaffold extends StatelessWidget with GameStoreMixin { final Widget Function(BuildContext, Widget?) builder; @@ -18,127 +12,16 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin { @override Widget build(BuildContext context) { - final interaction = PlatformUtils.isDesktop ? 'Click' : 'Tap'; return Scaffold( - appBar: AppBar( - title: GameStore.consumer( - builder: (context, store, child) => Text(store.connected - ? '${store.currentProfile.name} - Mudblock' - : 'Mudblock'), - ), - ), + appBar: const HomeAppBar(), body: ChangeNotifierProvider.value( value: gameStore, builder: builder, ), - endDrawer: Drawer( - child: GameStore.consumer( - builder: (context, store, child) => ListView( - children: [ - ListTile( - leading: const Image( - image: AssetImage('assets/images/logo/logo.png'), - width: 32, - height: 32, - ), - title: const Text('Mudblock'), - subtitle: FutureBuilder( - future: PackageInfo.fromPlatform().then((pkg) => pkg.version), - builder: (context, data) { - final version = data.data ?? '...'; - return Text('v$version'); - }, - ), - onTap: () => Navigator.pushNamed(context, Paths.about), - ), - const Divider(), - ListTile( - title: store.connected - ? Text(store.currentProfile.name) - : const Text('Not connected'), - subtitle: store.connected - ? Text( - '${store.currentProfile.host}:${store.currentProfile.port}', - ) - : Text('$interaction to connect'), - leading: const CircleAvatar(child: Icon(Icons.cable)), - onTap: store.connected - ? () async { - final updated = await Navigator.pushNamed( - context, - Paths.profile, - arguments: store.currentProfile, - ); - if (updated != null) { - await (updated as MUDProfile).save(); - store.loadSavedProfiles(); - } - } - : () { - store.selectProfileAndConnect(context); - }, - ), - if (store.connected) ...[ - ListTile( - title: const Text('Button Sets'), - leading: const Icon(GameButtonSetData.iconData), - onTap: () => Navigator.pushNamed(context, Paths.buttons), - ), - ListTile( - title: const Text('Aliases'), - leading: const Icon(Alias.iconData), - onTap: () => Navigator.pushNamed(context, Paths.aliases), - ), - ListTile( - title: const Text('Triggers'), - leading: const Icon(Trigger.iconData), - onTap: () => Navigator.pushNamed(context, Paths.triggers), - ), - ListTile( - title: const Text('Variables'), - leading: const Icon(Variable.iconData), - onTap: () => Navigator.pushNamed(context, Paths.variables), - ), - if (PlatformUtils.isDesktop) - ListTile( - title: const Text('Keyboard Shortcuts'), - leading: const Icon(Icons.keyboard), - onTap: () => Navigator.pushNamed(context, Paths.shortcuts), - ), - const Divider(), - ListTile( - title: const Text('Profiles'), - leading: const Icon(Icons.cable), - onTap: () => Navigator.pushNamed(context, Paths.profiles), - ), - ListTile( - title: const Text('Settings'), - leading: const Icon(Icons.settings), - onTap: () => Navigator.pushNamed(context, Paths.settings), - ), - ListTile( - title: const Text('Disconnect'), - leading: const Icon(Icons.exit_to_app), - onTap: () async { - Navigator.of(context).pop(); - await gameStore.disconnect(); - if (context.mounted) { - gameStore.selectProfileAndConnect(context); - } - }, - ), - ], - if (!store.connected) - ListTile( - title: const Text('Settings'), - leading: const Icon(Icons.settings), - onTap: () => Navigator.pushNamed(context, Paths.settings), - ), - ], - ), - ), - ), + endDrawer: const HomeDrawer(), ); } } + + diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 058c3bf..6277b9c 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -32,6 +32,7 @@ class _SettingsPageState extends State with GameStoreStateMixin { @override Widget build(BuildContext context) { + final baseFontSize = Theme.of(context).textTheme.bodyText1!.fontSize!; return Scaffold( appBar: AppBar( title: const Text('Settings'), @@ -89,6 +90,56 @@ class _SettingsPageState extends State with GameStoreStateMixin { 'Enabling this will make sure the screen doesn\'t turn off while a session is running.', ), ), + ListTile( + title: const Text('UI Font Size'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Change the font size of the entire UI.'), + Row( + children: [ + Expanded( + child: Slider( + value: globalSettings.uiTextScale, + onChanged: (value) => setState( + () => globalSettings.uiTextScale = value), + min: 0.5, + max: 2.0, + divisions: 15, + ), + ), + Text( + '${(globalSettings.uiTextScale * baseFontSize).toStringAsFixed(0)}pt (${(globalSettings.uiTextScale * 100).toStringAsFixed(0)}%)'), + ], + ), + ], + ), + ), + ListTile( + title: const Text('Game Output Font Size'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Change the font size of the game output text.'), + Row( + children: [ + Expanded( + child: Slider( + value: globalSettings.gameTextScale, + onChanged: (value) => setState( + () => globalSettings.gameTextScale = value), + min: 0.5, + max: 2.0, + divisions: 15, + ), + ), + Text( + '${(globalSettings.gameTextScale * baseFontSize).toStringAsFixed(0)}pt (${(globalSettings.gameTextScale*100).toStringAsFixed(0)}%)'), + ], + ), + ], + ), + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index da6e06b..be2c922 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -116,10 +116,11 @@ packages: ctelnet: dependency: "direct main" description: - path: "../ctelnet" - relative: true - source: path - version: "0.2.0" + name: ctelnet + sha256: "4432d54e336dc3674c20e09de9f834a2c103674d74c389ea2efefe4a3e6e77a8" + url: "https://pub.dev" + source: hosted + version: "0.3.0" cupertino_icons: dependency: "direct main" description: @@ -638,9 +639,10 @@ packages: terminal_color_parser: dependency: "direct main" description: - path: "../terminal_color_parser" - relative: true - source: path + name: terminal_color_parser + sha256: "2766aee8a2c1bba3cf3c87bdf37b24408517ae6aec9956bb29e320f6c9425937" + url: "https://pub.dev" + source: hosted version: "0.5.0" test_api: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index cced714..07dced4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,11 +28,12 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - ctelnet: ^0.3.0 + ctelnet: any # ctelnet: # path: ../ctelnet - terminal_color_parser: - path: ../terminal_color_parser + terminal_color_parser: any + # terminal_color_parser: + # path: ../terminal_color_parser flutter: sdk: flutter