diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 53bf461..50b0904 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -276,6 +276,7 @@ "titlebar": "Settings", "color_theme": "Color Theme", "brightness_mode": "Brightness Mode", + "display_scale": "Display Scale", "display_beta_warning": "Display beta warning on startup", "none": "None", "in_app": "In-app", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2528d2d..add7488 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,54 +6,9 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (2.4.3): - Flutter - - GoogleDataTransport (10.1.0): - - nanopb (~> 3.30910.0) - - PromisesObjC (~> 2.4) - - GoogleMLKit/BarcodeScanning (7.0.0): - - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 6.0.0) - - GoogleMLKit/MLKitCore (7.0.0): - - MLKitCommon (~> 12.0.0) - - GoogleToolboxForMac/Defines (4.2.1) - - GoogleToolboxForMac/Logger (4.2.1): - - GoogleToolboxForMac/Defines (= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (4.2.1)": - - GoogleToolboxForMac/Defines (= 4.2.1) - - GoogleUtilities/Environment (8.0.2): - - GoogleUtilities/Privacy - - GoogleUtilities/Logger (8.0.2): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (8.0.2) - - GoogleUtilities/UserDefaults (8.0.2): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GTMSessionFetcher/Core (3.5.0) - - MLImage (1.0.0-beta6) - - MLKitBarcodeScanning (6.0.0): - - MLKitCommon (~> 12.0) - - MLKitVision (~> 8.0) - - MLKitCommon (12.0.0): - - GoogleDataTransport (~> 10.0) - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GoogleUtilities/Logger (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLKitVision (8.0.0): - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLImage (= 1.0.0-beta6) - - MLKitCommon (~> 12.0) - - mobile_scanner (6.0.2): + - mobile_scanner (7.0.0): - Flutter - - GoogleMLKit/BarcodeScanning (~> 7.0.0) - - nanopb (3.30910.0): - - nanopb/decode (= 3.30910.0) - - nanopb/encode (= 3.30910.0) - - nanopb/decode (3.30910.0) - - nanopb/encode (3.30910.0) + - FlutterMacOS - package_info_plus (0.4.5): - Flutter - pasteboard (0.0.1): @@ -63,7 +18,6 @@ PODS: - FlutterMacOS - printing (1.0.0): - Flutter - - PromisesObjC (2.4.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -84,7 +38,7 @@ DEPENDENCIES: - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -96,20 +50,6 @@ DEPENDENCIES: - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - veilid (from `.symlinks/plugins/veilid/ios`) -SPEC REPOS: - trunk: - - GoogleDataTransport - - GoogleMLKit - - GoogleToolboxForMac - - GoogleUtilities - - GTMSessionFetcher - - MLImage - - MLKitBarcodeScanning - - MLKitCommon - - MLKitVision - - nanopb - - PromisesObjC - EXTERNAL SOURCES: camera_avfoundation: :path: ".symlinks/plugins/camera_avfoundation/ios" @@ -120,7 +60,7 @@ EXTERNAL SOURCES: flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" mobile_scanner: - :path: ".symlinks/plugins/mobile_scanner/ios" + :path: ".symlinks/plugins/mobile_scanner/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" pasteboard: @@ -143,26 +83,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf + camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 - GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 - GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d - GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56 - MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 - MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d - MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e - mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 - nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e612191..3a96d3e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -139,7 +139,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 02C44F9283ADDE9FAAA73512 /* [CP] Embed Pods Frameworks */, - 61BE8A90522682C17620991D /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -232,23 +231,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 61BE8A90522682C17620991D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3e31b44..a44fb7f 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> { title: translate('edit_account_page.remove_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.remove_account_confirm_message')) - .paddingLTRB(24, 24, 24, 0), - Text(translate('confirmation.are_you_sure')).paddingAll(8), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), + Text(translate('confirmation.are_you_sure')) + .paddingAll(8.scaled(context)), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), + Icon(Icons.cancel, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( @@ -89,10 +92,12 @@ class _EditAccountPageState extends WindowSetupState { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.check, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.yes')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])) - ]).paddingAll(24) + ]).paddingAll(24.scaled(context)) ])); if (confirmed != null && confirmed) { try { @@ -141,29 +146,36 @@ class _EditAccountPageState extends WindowSetupState { title: translate('edit_account_page.destroy_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.destroy_account_confirm_message')) - .paddingLTRB(24, 24, 24, 0), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), Text(translate( 'edit_account_page.destroy_account_confirm_message_details')) - .paddingLTRB(24, 24, 24, 0), - Text(translate('confirmation.are_you_sure')).paddingAll(8), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), + Text(translate('confirmation.are_you_sure')) + .paddingAll(24.scaled(context)), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.cancel, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.no')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])), ElevatedButton( onPressed: () { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.check, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.yes')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])) - ]).paddingAll(24) + ]).paddingAll(24.scaled(context)) ])); if (confirmed != null && confirmed) { try { @@ -250,6 +262,7 @@ class _EditAccountPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('edit_account_page.titlebar')), leading: Navigator.canPop(context) ? IconButton( @@ -277,6 +290,7 @@ class _EditAccountPageState extends WindowSetupState { const SignalStrengthMeterWidget(), IconButton( icon: const Icon(Icons.settings), + iconSize: 24.scaled(context), tooltip: translate('menu.settings_tooltip'), onPressed: () async { await GoRouterHelper(context).push('/settings'); diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 4977dc7..106366c 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -53,16 +53,16 @@ class EditProfileForm extends StatefulWidget { ..add(DiagnosticsProperty('initialValue', initialValue)); } - static const String formFieldName = 'name'; - static const String formFieldPronouns = 'pronouns'; - static const String formFieldAbout = 'about'; - static const String formFieldAvailability = 'availability'; - static const String formFieldFreeMessage = 'free_message'; - static const String formFieldAwayMessage = 'away_message'; - static const String formFieldBusyMessage = 'busy_message'; - static const String formFieldAvatar = 'avatar'; - static const String formFieldAutoAway = 'auto_away'; - static const String formFieldAutoAwayTimeout = 'auto_away_timeout'; + static const formFieldName = 'name'; + static const formFieldPronouns = 'pronouns'; + static const formFieldAbout = 'about'; + static const formFieldAvailability = 'availability'; + static const formFieldFreeMessage = 'free_message'; + static const formFieldAwayMessage = 'away_message'; + static const formFieldBusyMessage = 'busy_message'; + static const formFieldAvatar = 'avatar'; + static const formFieldAutoAway = 'auto_away'; + static const formFieldAutoAwayTimeout = 'auto_away_timeout'; } class _EditProfileFormState extends State { @@ -98,6 +98,7 @@ class _EditProfileFormState extends State { name: EditProfileForm.formFieldAvailability, initialValue: initialValue, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_availability'), hintText: translate('account.empty_busy_message')), @@ -110,7 +111,7 @@ class _EditProfileFormState extends State { Text(x == proto.Availability.AVAILABILITY_OFFLINE ? translate('availability.always_show_offline') : AvailabilityWidget.availabilityName(x)) - .paddingLTRB(8, 0, 0, 0), + .paddingLTRB(8.scaled(context), 0, 0, 0), ]))) .toList(), ); @@ -197,7 +198,7 @@ class _EditProfileFormState extends State { children: [ Row(children: [ const Spacer(), - AvatarWidget( + StyledAvatar( name: _currentValueName, size: 128, borderColor: border, @@ -218,6 +219,7 @@ class _EditProfileFormState extends State { }); }, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_name'), hintText: translate('account.empty_name')), @@ -233,6 +235,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.pronouns, maxLength: 64, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_pronouns'), hintText: translate('account.empty_pronouns')), @@ -245,6 +248,7 @@ class _EditProfileFormState extends State { maxLines: 8, minLines: 1, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_about'), hintText: translate('account.empty_about')), @@ -256,6 +260,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.freeMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_free_message'), hintText: translate('account.empty_free_message')), @@ -266,6 +271,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.awayMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_away_message'), hintText: translate('account.empty_away_message')), @@ -276,6 +282,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.busyMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_busy_message'), hintText: translate('account.empty_busy_message')), @@ -291,12 +298,13 @@ class _EditProfileFormState extends State { _currentValueAutoAway = v ?? false; }); }, - ).paddingLTRB(0, 0, 0, 16), + ).paddingLTRB(0, 0, 0, 16.scaled(context)), FormBuilderTextField( name: EditProfileForm.formFieldAutoAwayTimeout, enabled: _currentValueAutoAway, initialValue: _savedValue.autoAwayTimeout.toString(), decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), labelText: translate('account.form_auto_away_timeout'), ), validator: FormBuilderValidators.positiveNumber(), @@ -306,7 +314,7 @@ class _EditProfileFormState extends State { const Spacer(), Text(widget.instructions).toCenter().flexible(flex: 6), const Spacer(), - ]).paddingSymmetric(vertical: 16), + ]).paddingSymmetric(vertical: 16.scaled(context)), Row(children: [ const Spacer(), Builder(builder: (context) { @@ -322,12 +330,12 @@ class _EditProfileFormState extends State { onPressed: (networkReady && _isModified) ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ Icon(networkReady ? Icons.check : Icons.hourglass_empty, - size: 16) - .paddingLTRB(0, 0, 4, 0), + size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), Text(networkReady ? widget.submitText : widget.submitDisabledText) - .paddingLTRB(0, 0, 4, 0) + .paddingLTRB(0, 0, 4.scaled(context), 0) ]), ); }), @@ -363,5 +371,5 @@ class _EditProfileFormState extends State { late AccountSpec _savedValue; late bool _currentValueAutoAway; late String _currentValueName; - bool _isModified = false; + var _isModified = false; } diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index 07034df..5012527 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -94,6 +94,7 @@ class _NewAccountPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('new_account_page.titlebar')), leading: GoRouterHelper(context).canPop() ? IconButton( @@ -111,6 +112,7 @@ class _NewAccountPageState extends WindowSetupState { const SignalStrengthMeterWidget(), IconButton( icon: const Icon(Icons.settings), + iconSize: 24.scaled(context), tooltip: translate('menu.settings_tooltip'), onPressed: () async { await GoRouterHelper(context).push('/settings'); diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index 7c971e0..a551ffd 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -164,6 +164,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('show_recovery_key_page.titlebar')), actions: [ const SignalStrengthMeterWidget(), diff --git a/lib/app.dart b/lib/app.dart index 802b0d7..7b1e0c8 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -31,7 +31,7 @@ class VeilidChatApp extends StatelessWidget { super.key, }); - static const String name = 'VeilidChat'; + static const name = 'VeilidChat'; final ThemeData initialThemeData; diff --git a/lib/chat/views/chat_builders/vc_composer_widget.dart b/lib/chat/views/chat_builders/vc_composer_widget.dart index b3eb1e5..f470e9b 100644 --- a/lib/chat/views/chat_builders/vc_composer_widget.dart +++ b/lib/chat/views/chat_builders/vc_composer_widget.dart @@ -355,6 +355,7 @@ class _VcComposerState extends State { borderRadius: BorderRadius.all(Radius.circular( 8 * config.borderRadiusScale))), hintText: widget.hintText, + hintMaxLines: 1, hintStyle: chatTheme.typography.bodyMedium.copyWith( color: widget.hintColor ?? chatTheme.colors.onSurface diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 5191fdd..b1645a4 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -48,7 +48,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { selected: selected, ); - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, size: 32, borderColor: scaleTheme.config.useVisualIndicators @@ -64,7 +64,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { textStyle: theme.textTheme.titleLarge!, ); - return SliderTile( + return StyledSlideTile( key: ValueKey(_localConversationRecordKey), disabled: _disabled, selected: selected, @@ -82,7 +82,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { }); }, endActions: [ - SliderTileAction( + SlideTileAction( //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contact_invitation/views/contact_invitation_item_widget.dart b/lib/contact_invitation/views/contact_invitation_item_widget.dart index 544e5db..b86a833 100644 --- a/lib/contact_invitation/views/contact_invitation_item_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_item_widget.dart @@ -44,7 +44,7 @@ class ContactInvitationItemWidget extends StatelessWidget { title = contactInvitationRecord.message; } - return SliderTile( + return StyledSlideTile( key: ObjectKey(contactInvitationRecord), disabled: tileDisabled, selected: selected, @@ -67,7 +67,7 @@ class ContactInvitationItemWidget extends StatelessWidget { ))); }, endActions: [ - SliderTileAction( + SlideTileAction( // icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index 75ef0a8..55fac39 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -1,37 +1,34 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; -import '../../theme/theme.dart'; class AvailabilityWidget extends StatelessWidget { const AvailabilityWidget( {required this.availability, required this.color, this.vertical = true, - this.size = 32, super.key}); - static Widget availabilityIcon(proto.Availability availability, Color color, - {double size = 24}) { + static Widget availabilityIcon( + proto.Availability availability, + Color color, + ) { late final Widget icon; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: icon = SvgPicture.asset('assets/images/toilet.svg', - width: size, - height: size, colorFilter: ColorFilter.mode(color, BlendMode.srcATop)); case proto.Availability.AVAILABILITY_BUSY: - icon = Icon(Icons.event_busy, size: size); + icon = const Icon(Icons.event_busy, applyTextScaling: true); case proto.Availability.AVAILABILITY_FREE: - icon = Icon(Icons.event_available, size: size); + icon = const Icon(Icons.event_available, applyTextScaling: true); case proto.Availability.AVAILABILITY_OFFLINE: - icon = Icon(Icons.cloud_off, size: size); + icon = const Icon(Icons.cloud_off, applyTextScaling: true); case proto.Availability.AVAILABILITY_UNSPECIFIED: - icon = Icon(Icons.question_mark, size: size); + icon = const Icon(Icons.question_mark, applyTextScaling: true); } return icon; } @@ -59,23 +56,17 @@ class AvailabilityWidget extends StatelessWidget { final textTheme = theme.textTheme; final name = availabilityName(availability); - final icon = availabilityIcon(availability, color, size: size * 2 / 3); + final icon = availabilityIcon(availability, color); return vertical - ? ConstrainedBox( - constraints: BoxConstraints.tightFor(width: size), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - icon, - Text(name, style: textTheme.labelSmall!.copyWith(color: color)) - .fit(fit: BoxFit.scaleDown) - ])) - : ConstrainedBox( - constraints: BoxConstraints.tightFor(height: size), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - icon, - Text(name, style: textTheme.labelLarge!.copyWith(color: color)) - .paddingLTRB(size / 4, 0, 0, 0) - ])); + ? Column(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + ]) + : Row(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(' $name', style: textTheme.labelLarge!.copyWith(color: color)) + ]); } //////////////////////////////////////////////////////////////////////////// @@ -83,7 +74,6 @@ class AvailabilityWidget extends StatelessWidget { final proto.Availability availability; final Color color; final bool vertical; - final double size; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -92,7 +82,6 @@ class AvailabilityWidget extends StatelessWidget { ..add( DiagnosticsProperty('availability', availability)) ..add(DiagnosticsProperty('vertical', vertical)) - ..add(DoubleProperty('size', size)) ..add(ColorProperty('color', color)); } } diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index e206570..5393fb3 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -34,7 +34,7 @@ class ContactItemWidget extends StatelessWidget { final title = _contact.displayName; final subtitle = _contact.profile.status; - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, size: 34, borderColor: _disabled @@ -49,7 +49,7 @@ class ContactItemWidget extends StatelessWidget { textStyle: theme.textTheme.titleLarge!, ); - return SliderTile( + return StyledSlideTile( key: ObjectKey(_contact), disabled: _disabled, selected: _selected, @@ -69,7 +69,7 @@ class ContactItemWidget extends StatelessWidget { }), startActions: [ if (_onDoubleTap != null) - SliderTileAction( + SlideTileAction( //icon: Icons.edit, label: translate('button.chat'), actionScale: ScaleKind.secondary, @@ -81,7 +81,7 @@ class ContactItemWidget extends StatelessWidget { ], endActions: [ if (_onTap != null) - SliderTileAction( + SlideTileAction( //icon: Icons.edit, label: translate('button.edit'), actionScale: ScaleKind.secondary, @@ -91,7 +91,7 @@ class ContactItemWidget extends StatelessWidget { }), ), if (_onDelete != null) - SliderTileAction( + SlideTileAction( //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart index 26a6f0d..6760a16 100644 --- a/lib/contacts/views/contacts_page.dart +++ b/lib/contacts/views/contacts_page.dart @@ -40,6 +40,7 @@ class _ContactsPageState extends State { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text( !enableSplit && enableRight ? translate('contacts_dialog.edit_contact') diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index 514f019..a456b1d 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -116,7 +116,7 @@ class _EditContactFormState extends State { children: [ Row(children: [ const Spacer(), - AvatarWidget( + StyledAvatar( name: _currentValueNickname.isNotEmpty ? _currentValueNickname : widget.contact.profile.name, diff --git a/lib/keyboard_shortcuts.dart b/lib/keyboard_shortcuts.dart index 6708d72..7b952c8 100644 --- a/lib/keyboard_shortcuts.dart +++ b/lib/keyboard_shortcuts.dart @@ -32,6 +32,14 @@ class DeveloperPageIntent extends Intent { const DeveloperPageIntent(); } +class DisplayScaleUpIntent extends Intent { + const DisplayScaleUpIntent(); +} + +class DisplayScaleDownIntent extends Intent { + const DisplayScaleDownIntent(); +} + class KeyboardShortcuts extends StatelessWidget { const KeyboardShortcuts({required this.child, super.key}); @@ -57,7 +65,7 @@ class KeyboardShortcuts extends StatelessWidget { }); } - void changeBrightness(BuildContext context) { + void _changeBrightness(BuildContext context) { singleFuture(this, () async { final prefs = PreferencesRepository.instance.value; @@ -79,7 +87,7 @@ class KeyboardShortcuts extends StatelessWidget { }); } - void changeColor(BuildContext context) { + void _changeColor(BuildContext context) { singleFuture(this, () async { final prefs = PreferencesRepository.instance.value; final oldColor = prefs.themePreference.colorPreference; @@ -100,6 +108,54 @@ class KeyboardShortcuts extends StatelessWidget { }); } + void _displayScaleUp(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale); + if (oldIndex == maxDisplayScaleIndex) { + return; + } + final newIndex = oldIndex + 1; + final newDisplayScaleName = indexToDisplayScaleName(newIndex); + + log.info('Changing display scale to $newDisplayScaleName'); + + final newPrefs = prefs.copyWith( + themePreference: prefs.themePreference + .copyWith(displayScale: indexToDisplayScale(newIndex))); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + + void _displayScaleDown(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale); + if (oldIndex == 0) { + return; + } + final newIndex = oldIndex - 1; + final newDisplayScaleName = indexToDisplayScaleName(newIndex); + + log.info('Changing display scale to $newDisplayScaleName'); + + final newPrefs = prefs.copyWith( + themePreference: prefs.themePreference + .copyWith(displayScale: indexToDisplayScale(newIndex))); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + void _attachDetach(BuildContext context) { singleFuture(this, () async { if (ProcessorRepository.instance.processorConnectionState.isAttached) { @@ -125,44 +181,88 @@ class KeyboardShortcuts extends StatelessWidget { @override Widget build(BuildContext context) => ThemeSwitcher( builder: (context) => Shortcuts( - shortcuts: const { - SingleActivator( + shortcuts: { + ////////////////////////// Reload Theme + const SingleActivator( LogicalKeyboardKey.keyR, control: true, alt: true, - ): ReloadThemeIntent(), - SingleActivator( + ): const ReloadThemeIntent(), + ////////////////////////// Switch Brightness + const SingleActivator( LogicalKeyboardKey.keyB, control: true, alt: true, - ): ChangeBrightnessIntent(), - SingleActivator( + ): const ChangeBrightnessIntent(), + ////////////////////////// Change Color + const SingleActivator( LogicalKeyboardKey.keyC, control: true, alt: true, - ): ChangeColorIntent(), - SingleActivator( - LogicalKeyboardKey.keyA, - control: true, - alt: true, - ): AttachDetachIntent(), - SingleActivator( + ): const ChangeColorIntent(), + ////////////////////////// Attach/Detach + if (kIsDebugMode) + const SingleActivator( + LogicalKeyboardKey.keyA, + control: true, + alt: true, + ): const AttachDetachIntent(), + ////////////////////////// Show Developer Page + const SingleActivator( LogicalKeyboardKey.keyD, control: true, alt: true, - ): DeveloperPageIntent(), + ): const DeveloperPageIntent(), + ////////////////////////// Display Scale Up + SingleActivator( + LogicalKeyboardKey.equal, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.equal, + shift: true, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.add, + shift: true, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.numpadAdd, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + ////////////////////////// Display Scale Down + SingleActivator( + LogicalKeyboardKey.minus, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleDownIntent(), + SingleActivator( + LogicalKeyboardKey.numpadSubtract, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleDownIntent(), }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( onInvoke: (intent) => reloadTheme(context)), ChangeBrightnessIntent: CallbackAction( - onInvoke: (intent) => changeBrightness(context)), + onInvoke: (intent) => _changeBrightness(context)), ChangeColorIntent: CallbackAction( - onInvoke: (intent) => changeColor(context)), + onInvoke: (intent) => _changeColor(context)), AttachDetachIntent: CallbackAction( onInvoke: (intent) => _attachDetach(context)), DeveloperPageIntent: CallbackAction( onInvoke: (intent) => _developerPage(context)), + DisplayScaleUpIntent: CallbackAction( + onInvoke: (intent) => _displayScaleUp(context)), + DisplayScaleDownIntent: CallbackAction( + onInvoke: (intent) => _displayScaleDown(context)), }, child: Focus(autofocus: true, child: child)))); ///////////////////////////////////////////////////////// diff --git a/lib/layout/default_app_bar.dart b/lib/layout/default_app_bar.dart index b9c0b41..de4536a 100644 --- a/lib/layout/default_app_bar.dart +++ b/lib/layout/default_app_bar.dart @@ -2,21 +2,25 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../theme/theme.dart'; + class DefaultAppBar extends AppBar { DefaultAppBar( - {super.title, + {required BuildContext context, + super.title, super.flexibleSpace, super.key, Widget? leading, super.actions}) : super( + toolbarHeight: 40.scaled(context), leading: leading ?? Container( - margin: const EdgeInsets.all(4), + margin: const EdgeInsets.all(4).scaled(context), decoration: BoxDecoration( color: Colors.black.withAlpha(32), shape: BoxShape.circle), - child: - SvgPicture.asset('assets/images/vlogo.svg', height: 24) - .paddingAll(4))); + child: SvgPicture.asset('assets/images/vlogo.svg', + height: 24.scaled(context)) + .paddingAll(4))); } diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 0863b1f..7c82148 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -95,9 +95,9 @@ class _DrawerMenuState extends State { activeBorder = scale.primary; } - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, - size: 34, + size: 34.scaled(context), borderColor: border, foregroundColor: loggedIn ? scale.primaryText : scale.subtleText, backgroundColor: loggedIn ? scale.primary : scale.elementBackground, @@ -107,7 +107,8 @@ class _DrawerMenuState extends State { return AnimatedPadding( padding: EdgeInsets.fromLTRB(selected ? 0 : 8, selected ? 0 : 2, - selected ? 0 : 8, selected ? 0 : 2), + selected ? 0 : 8, selected ? 0 : 2) + .scaled(context), duration: const Duration(milliseconds: 50), child: MenuItemWidget( title: name, @@ -144,7 +145,7 @@ class _DrawerMenuState extends State { (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) ? null : activeBorder, - minHeight: 48, + minHeight: 48.scaled(context), )); } @@ -196,7 +197,8 @@ class _DrawerMenuState extends State { color: scaleScheme.errorScale.subtleBorder, borderRadius: 12 * scaleConfig.borderRadiusScale), ); - loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8)); + loggedInAccounts + .add(loggedInAccount.paddingLTRB(0, 0, 0, 8.scaled(context))); } else { // Account is not logged in final scale = theme.extension()!.grayScale; @@ -246,8 +248,8 @@ class _DrawerMenuState extends State { } return IconButton( icon: icon, + padding: const EdgeInsets.all(12), color: border, - constraints: const BoxConstraints.expand(height: 48, width: 48), style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.hovered)) { @@ -286,7 +288,10 @@ class _DrawerMenuState extends State { final scale = scaleScheme.scale(_scaleKind); final settingsButton = _getButton( - icon: const Icon(Icons.settings), + icon: const Icon( + Icons.settings, + applyTextScaling: true, + ), tooltip: translate('menu.settings_tooltip'), scale: scale, scaleConfig: scaleConfig, @@ -295,7 +300,10 @@ class _DrawerMenuState extends State { }).paddingLTRB(0, 0, 16, 0); final addButton = _getButton( - icon: const Icon(Icons.add), + icon: const Icon( + Icons.add, + applyTextScaling: true, + ), tooltip: translate('menu.add_account_tooltip'), scale: scale, scaleConfig: scaleConfig, @@ -364,7 +372,7 @@ class _DrawerMenuState extends State { // : null) // .paddingLTRB(0, 0, 16, 0), GestureDetector( - onLongPress: () async { + onLongPress: () { context .findAncestorWidgetOfExactType()! .reloadTheme(context); diff --git a/lib/layout/home/drawer_menu/menu_item_widget.dart b/lib/layout/home/drawer_menu/menu_item_widget.dart index 1255458..80e466b 100644 --- a/lib/layout/home/drawer_menu/menu_item_widget.dart +++ b/lib/layout/home/drawer_menu/menu_item_widget.dart @@ -2,6 +2,8 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import '../../../theme/views/preferences/preferences.dart'; + class MenuItemWidget extends StatelessWidget { const MenuItemWidget({ required this.title, @@ -81,7 +83,7 @@ class MenuItemWidget extends StatelessWidget { hoverColor: footerButtonIconHoverColor, icon: Icon( footerButtonIcon, - size: 24, + size: 24.scaled(context), ), onPressed: footerCallback), ], diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index a2966a6..5ae1180 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -28,73 +28,81 @@ class _HomeAccountReadyState extends State { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return IconButton( - icon: const Icon(Icons.menu), - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 40, width: 40), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - scaleConfig.preferBorders - ? scale.primaryScale.hoverElementBackground - : scale.primaryScale.hoverBorder), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - side: !scaleConfig.useVisualIndicators - ? BorderSide.none - : BorderSide( - strokeAlign: BorderSide.strokeAlignCenter, - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - width: 2), - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - )), - tooltip: translate('menu.accounts_menu_tooltip'), - onPressed: () async { - final ctrl = context.read(); - await ctrl.toggle?.call(); - }); + return AspectRatio( + aspectRatio: 1, + child: IconButton( + icon: const Icon( + Icons.menu, + applyTextScaling: true, + ), + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + scaleConfig.preferBorders + ? scale.primaryScale.hoverElementBackground + : scale.primaryScale.hoverBorder), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + side: !scaleConfig.useVisualIndicators + ? BorderSide.none + : BorderSide( + strokeAlign: BorderSide.strokeAlignCenter, + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + width: 2), + borderRadius: BorderRadius.all(Radius.circular( + 8 * scaleConfig.borderRadiusScale))), + )), + tooltip: translate('menu.accounts_menu_tooltip'), + onPressed: () async { + final ctrl = context.read(); + await ctrl.toggle?.call(); + })); }); Widget buildContactsButton() => Builder(builder: (context) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return IconButton( - icon: const Icon(Icons.contacts), - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 40, width: 40), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - scaleConfig.preferBorders - ? scale.primaryScale.hoverElementBackground - : scale.primaryScale.hoverBorder), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - side: !scaleConfig.useVisualIndicators - ? BorderSide.none - : BorderSide( - strokeAlign: BorderSide.strokeAlignCenter, - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - width: 2), - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - )), - tooltip: translate('menu.contacts_tooltip'), - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => const ContactsPage(), + return AspectRatio( + aspectRatio: 1, + child: IconButton( + icon: const Icon( + Icons.contacts, + applyTextScaling: true, ), - ); - }); + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + scaleConfig.preferBorders + ? scale.primaryScale.hoverElementBackground + : scale.primaryScale.hoverBorder), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + side: !scaleConfig.useVisualIndicators + ? BorderSide.none + : BorderSide( + strokeAlign: BorderSide.strokeAlignCenter, + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + width: 2), + borderRadius: BorderRadius.all(Radius.circular( + 8 * scaleConfig.borderRadiusScale))), + )), + tooltip: translate('menu.contacts_tooltip'), + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ContactsPage(), + ), + ); + })); }); Widget buildLeftPane(BuildContext context) => Builder( @@ -112,14 +120,17 @@ class _HomeAccountReadyState extends State { ? scale.primaryScale.subtleBackground : scale.primaryScale.subtleBorder, child: Column(children: [ - Row(children: [ - buildMenuButton().paddingLTRB(0, 0, 8, 0), - ProfileWidget( - profile: profile, - showPronouns: false, - ).expanded(), - buildContactsButton().paddingLTRB(8, 0, 0, 0), - ]).paddingAll(8), + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildMenuButton().paddingLTRB(0, 0, 8, 0), + ProfileWidget( + profile: profile, + showPronouns: false, + ).expanded(), + buildContactsButton().paddingLTRB(8, 0, 0, 0), + ])).paddingAll(8), const ChatListWidget().expanded() ])); }))); diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart index 82f3555..8dddefd 100644 --- a/lib/notifications/views/notifications_preferences.dart +++ b/lib/notifications/views/notifications_preferences.dart @@ -1,24 +1,13 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../settings/settings.dart'; import '../../theme/theme.dart'; import '../notifications.dart'; -const String formFieldDisplayBetaWarning = 'displayBetaWarning'; -const String formFieldEnableBadge = 'enableBadge'; -const String formFieldEnableNotifications = 'enableNotifications'; -const String formFieldMessageNotificationContent = 'messageNotificationContent'; -const String formFieldInvitationAcceptMode = 'invitationAcceptMode'; -const String formFieldInvitationAcceptSound = 'invitationAcceptSound'; -const String formFieldMessageReceivedMode = 'messageReceivedMode'; -const String formFieldMessageReceivedSound = 'messageReceivedSound'; -const String formFieldMessageSentSound = 'messageSentSound'; - Widget buildSettingsPageNotificationPreferences( - {required BuildContext context, required void Function() onChanged}) { + {required BuildContext context}) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; @@ -33,7 +22,6 @@ Widget buildSettingsPageNotificationPreferences( final newPrefs = preferencesRepository.value .copyWith(notificationsPreference: newNotificationsPreference); await preferencesRepository.set(newPrefs); - onChanged(); } List> notificationModeItems() { @@ -127,66 +115,45 @@ Widget buildSettingsPageNotificationPreferences( ), child: Column(mainAxisSize: MainAxisSize.min, children: [ // Display Beta Warning - FormBuilderCheckbox( - name: formFieldDisplayBetaWarning, - title: Text(translate('settings_page.display_beta_warning'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.displayBetaWarning, + StyledCheckbox( + label: translate('settings_page.display_beta_warning'), + value: notificationsPreference.displayBetaWarning, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(displayBetaWarning: value); await updatePreferences(newNotificationsPreference); }), // Enable Badge - FormBuilderCheckbox( - name: formFieldEnableBadge, - title: Text(translate('settings_page.enable_badge'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.enableBadge, + StyledCheckbox( + label: translate('settings_page.enable_badge'), + value: notificationsPreference.enableBadge, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(enableBadge: value); await updatePreferences(newNotificationsPreference); }), // Enable Notifications - FormBuilderCheckbox( - name: formFieldEnableNotifications, - title: Text(translate('settings_page.enable_notifications'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.enableNotifications, + StyledCheckbox( + label: translate('settings_page.enable_notifications'), + value: notificationsPreference.enableNotifications, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(enableNotifications: value); await updatePreferences(newNotificationsPreference); }), - - FormBuilderDropdown( - name: formFieldMessageNotificationContent, - isDense: false, - decoration: InputDecoration( - labelText: translate('settings_page.message_notification_content')), - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.messageNotificationContent, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference.copyWith( - messageNotificationContent: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: messageNotificationContentItems(), - ).paddingLTRB(0, 4, 0, 4), + value: notificationsPreference.messageNotificationContent, + decoratorLabel: translate('settings_page.message_notification_content'), + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(messageNotificationContent: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)), // Notifications Table( @@ -199,21 +166,21 @@ Widget buildSettingsPageNotificationPreferences( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), Text(translate('settings_page.delivery'), textAlign: TextAlign.center, style: textTheme.titleMedium!.copyWith( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), Text(translate('settings_page.sound'), textAlign: TextAlign.center, style: textTheme.titleMedium!.copyWith( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), ]), TableRow(children: [ // Invitation accepted @@ -221,36 +188,31 @@ Widget buildSettingsPageNotificationPreferences( textAlign: TextAlign.right, translate('settings_page.invitation_accepted')) .paddingAll(8), - FormBuilderDropdown( - name: formFieldInvitationAcceptMode, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onInvitationAcceptedMode, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onInvitationAcceptedMode: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: notificationModeItems(), - ).paddingAll(4), - FormBuilderDropdown( - name: formFieldInvitationAcceptSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onInvitationAcceptedSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onInvitationAcceptedSound: value); - await updatePreferences(newNotificationsPreference); - }, + value: notificationsPreference.onInvitationAcceptedMode, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onInvitationAcceptedMode: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingAll(4.scaled(context)), + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onInvitationAcceptedSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onInvitationAcceptedSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), // Message received TableRow(children: [ @@ -258,36 +220,31 @@ Widget buildSettingsPageNotificationPreferences( textAlign: TextAlign.right, translate('settings_page.message_received')) .paddingAll(8), - FormBuilderDropdown( - name: formFieldMessageReceivedMode, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageReceivedMode, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageReceivedMode: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: notificationModeItems(), + value: notificationsPreference.onMessageReceivedMode, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageReceivedMode: value); + await updatePreferences(newNotificationsPreference); + }, ).paddingAll(4), - FormBuilderDropdown( - name: formFieldMessageReceivedSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageReceivedSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageReceivedSound: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onMessageReceivedSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageReceivedSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), // Message sent @@ -295,25 +252,23 @@ Widget buildSettingsPageNotificationPreferences( Text( textAlign: TextAlign.right, translate('settings_page.message_sent')) - .paddingAll(8), + .paddingAll(8.scaled(context)), const SizedBox.shrink(), - FormBuilderDropdown( - name: formFieldMessageSentSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageSentSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageSentSound: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onMessageSentSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageSentSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), ]) - ]).paddingAll(8), + ]).paddingAll(8.scaled(context)), ); } diff --git a/lib/router/views/router_shell.dart b/lib/router/views/router_shell.dart index 164c452..d22129f 100644 --- a/lib/router/views/router_shell.dart +++ b/lib/router/views/router_shell.dart @@ -1,7 +1,9 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '../../keyboard_shortcuts.dart'; import '../../notifications/notifications.dart'; +import '../../settings/settings.dart'; import '../../theme/theme.dart'; class RouterShell extends StatelessWidget { @@ -10,7 +12,13 @@ class RouterShell extends StatelessWidget { @override Widget build(BuildContext context) => PopControl( dismissible: false, - child: NotificationsWidget(child: KeyboardShortcuts(child: _child))); + child: AsyncBlocBuilder( + builder: (context, state) => MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: + TextScaler.linear(state.themePreference.displayScale)), + child: NotificationsWidget( + child: KeyboardShortcuts(child: _child))))); final Widget _child; } diff --git a/lib/settings/models/preferences.dart b/lib/settings/models/preferences.dart index 3ef683e..a7432d6 100644 --- a/lib/settings/models/preferences.dart +++ b/lib/settings/models/preferences.dart @@ -20,7 +20,7 @@ sealed class LockPreference with _$LockPreference { factory LockPreference.fromJson(dynamic json) => _$LockPreferenceFromJson(json as Map); - static const LockPreference defaults = LockPreference(); + static const defaults = LockPreference(); } // Theme supports multiple translations @@ -49,5 +49,5 @@ sealed class Preferences with _$Preferences { factory Preferences.fromJson(dynamic json) => _$PreferencesFromJson(json as Map); - static const Preferences defaults = Preferences(); + static const defaults = Preferences(); } diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 2a05f08..679cdd5 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -1,7 +1,6 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; @@ -11,29 +10,16 @@ import '../theme/theme.dart'; import '../veilid_processor/veilid_processor.dart'; import 'settings.dart'; -class SettingsPage extends StatefulWidget { +class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); - @override - SettingsPageState createState() => SettingsPageState(); -} - -class SettingsPageState extends State { - final _formKey = GlobalKey(); - static const String formFieldTheme = 'theme'; - static const String formFieldBrightness = 'brightness'; - - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) => AsyncBlocBuilder( builder: (context, state) => ThemeSwitcher.withTheme( - builder: (_, switcher, theme) => StyledScaffold( + builder: (_, switcher, theme) => StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('settings_page.titlebar')), leading: IconButton( icon: const Icon(Icons.arrow_back), @@ -43,30 +29,33 @@ class SettingsPageState extends State { const SignalStrengthMeterWidget() .paddingLTRB(16, 0, 16, 0), ]), - body: ThemeSwitchingArea( - child: FormBuilder( - key: _formKey, - child: ListView( - padding: const EdgeInsets.all(8), - children: [ - buildSettingsPageColorPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})) - .paddingLTRB(0, 8, 0, 0), - buildSettingsPageBrightnessPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})), - buildSettingsPageWallpaperPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})), - buildSettingsPageNotificationPreferences( - context: context, - onChanged: () => setState(() {})), - ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), + body: ListView( + padding: const EdgeInsets.all(8).scaled(context), + children: [ + buildSettingsPageColorPreferences( + context: context, + switcher: switcher, ), - ).paddingSymmetric(horizontal: 8, vertical: 8), - )))); + buildSettingsPageBrightnessPreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageDisplayScalePreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageWallpaperPreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageNotificationPreferences( + context: context, + ), + ] + .map((x) => x.paddingLTRB(0, 0, 0, 8.scaled(context))) + .toList(), + ).paddingSymmetric(vertical: 4.scaled(context)), + ).paddingSymmetric( + horizontal: 8.scaled(context), vertical: 8.scaled(context)), + )); } diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 314e28a..05c5f55 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -308,6 +308,13 @@ ThemeData contrastGenerator({ side: elementBorderWidgetStateProperty(), backgroundColor: elementBackgroundWidgetStateProperty())); + final sliderTheme = SliderThemeData.fromPrimaryColors( + primaryColor: scheme.primaryScale.borderText, + primaryColorDark: scheme.primaryScale.border, + primaryColorLight: scheme.primaryScale.border, + valueIndicatorTextStyle: textTheme.labelMedium! + .copyWith(color: scheme.primaryScale.borderText)); + final themeData = baseThemeData.copyWith( // chipTheme: baseThemeData.chipTheme.copyWith( // backgroundColor: scaleScheme.primaryScale.elementBackground, @@ -316,6 +323,7 @@ ThemeData contrastGenerator({ // checkmarkColor: scaleScheme.primaryScale.border, // side: BorderSide(color: scaleScheme.primaryScale.border)), elevatedButtonTheme: elevatedButtonTheme, + sliderTheme: sliderTheme, textSelectionTheme: TextSelectionThemeData( cursorColor: scheme.primaryScale.appText, selectionColor: scheme.primaryScale.appText.withAlpha(0x7F), diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index c3217ea..755bd54 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -132,6 +132,13 @@ class ScaleTheme extends ThemeExtension { iconColor: elementColorWidgetStateProperty(), )); + final sliderTheme = SliderThemeData.fromPrimaryColors( + primaryColor: scheme.primaryScale.hoverBorder, + primaryColorDark: scheme.primaryScale.border, + primaryColorLight: scheme.primaryScale.border, + valueIndicatorTextStyle: textTheme.labelMedium! + .copyWith(color: scheme.primaryScale.borderText)); + final themeData = baseThemeData.copyWith( scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( thumbColor: WidgetStateProperty.resolveWith((states) { @@ -183,6 +190,7 @@ class ScaleTheme extends ThemeExtension { elevatedButtonTheme: elevatedButtonTheme, inputDecorationTheme: ScaleInputDecoratorTheme(scheme, config, textTheme), + sliderTheme: sliderTheme, extensions: >[scheme, config, this]); return themeData; diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index aaad52d..44d06d8 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -61,7 +61,7 @@ sealed class ThemePreferences with _$ThemePreferences { factory ThemePreferences.fromJson(dynamic json) => _$ThemePreferencesFromJson(json as Map); - static const ThemePreferences defaults = ThemePreferences(); + static const defaults = ThemePreferences(); } extension ThemePreferencesExt on ThemePreferences { diff --git a/lib/theme/views/enter_password.dart b/lib/theme/views/enter_password.dart index fc876da..f28b69e 100644 --- a/lib/theme/views/enter_password.dart +++ b/lib/theme/views/enter_password.dart @@ -32,7 +32,7 @@ class _EnterPasswordDialogState extends State { final passwordController = TextEditingController(); final focusNode = FocusNode(); final formKey = GlobalKey(); - bool _passwordVisible = false; + var _passwordVisible = false; @override void initState() { @@ -47,7 +47,6 @@ class _EnterPasswordDialogState extends State { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; diff --git a/lib/theme/views/brightness_preferences.dart b/lib/theme/views/preferences/brightness_preferences.dart similarity index 59% rename from lib/theme/views/brightness_preferences.dart rename to lib/theme/views/preferences/brightness_preferences.dart index 7a1bb1d..a149483 100644 --- a/lib/theme/views/brightness_preferences.dart +++ b/lib/theme/views/preferences/brightness_preferences.dart @@ -1,14 +1,12 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../settings/settings.dart'; -import '../models/models.dart'; +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; -const String formFieldBrightness = 'brightness'; - -List> _getBrightnessDropdownItems() { +List> _getBrightnessDropdownItems() { const brightnessPrefs = BrightnessPreference.values; final brightnessNames = { BrightnessPreference.system: translate('brightness.system'), @@ -22,25 +20,21 @@ List> _getBrightnessDropdownItems() { } Widget buildSettingsPageBrightnessPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { + {required BuildContext context, required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return FormBuilderDropdown( - name: formFieldBrightness, - decoration: InputDecoration( - label: Text(translate('settings_page.brightness_mode'))), + + return StyledDropdown( items: _getBrightnessDropdownItems(), - initialValue: themePreferences.brightnessPreference, + value: themePreferences.brightnessPreference, + decoratorLabel: translate('settings_page.brightness_mode'), onChanged: (value) async { - final newThemePrefs = themePreferences.copyWith( - brightnessPreference: value as BrightnessPreference); + final newThemePrefs = + themePreferences.copyWith(brightnessPreference: value); final newPrefs = preferencesRepository.value .copyWith(themePreference: newThemePrefs); await preferencesRepository.set(newPrefs); switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); }); } diff --git a/lib/theme/views/color_preferences.dart b/lib/theme/views/preferences/color_preferences.dart similarity index 54% rename from lib/theme/views/color_preferences.dart rename to lib/theme/views/preferences/color_preferences.dart index a9a8841..2c14a93 100644 --- a/lib/theme/views/color_preferences.dart +++ b/lib/theme/views/preferences/color_preferences.dart @@ -1,16 +1,12 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; -import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../settings/settings.dart'; -import '../models/models.dart'; +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; -const String formFieldTheme = 'theme'; -const String _kSwitchTheme = 'switchTheme'; - -List> _getThemeDropdownItems() { +List> _getThemeDropdownItems() { const colorPrefs = ColorPreference.values; final colorNames = { ColorPreference.scarlet: translate('themes.scarlet'), @@ -34,27 +30,20 @@ List> _getThemeDropdownItems() { } Widget buildSettingsPageColorPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { + {required BuildContext context, required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return FormBuilderDropdown( - name: formFieldTheme, - decoration: - InputDecoration(label: Text(translate('settings_page.color_theme'))), - items: _getThemeDropdownItems(), - initialValue: themePreferences.colorPreference, - onChanged: (value) { - singleFuture(_kSwitchTheme, () async { - final newThemePrefs = themePreferences.copyWith( - colorPreference: value as ColorPreference); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - }); + return StyledDropdown( + items: _getThemeDropdownItems(), + value: themePreferences.colorPreference, + decoratorLabel: translate('settings_page.color_theme'), + onChanged: (value) async { + final newThemePrefs = themePreferences.copyWith(colorPreference: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); }); } diff --git a/lib/theme/views/preferences/display_scale_preferences.dart b/lib/theme/views/preferences/display_scale_preferences.dart new file mode 100644 index 0000000..a850ca1 --- /dev/null +++ b/lib/theme/views/preferences/display_scale_preferences.dart @@ -0,0 +1,109 @@ +import 'dart:math'; + +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; + +const _scales = [ + 1 / (1 + 1 / 2), + 1 / (1 + 1 / 3), + 1 / (1 + 1 / 4), + 1, + 1 + (1 / 4), + 1 + (1 / 2), + 1 + (1 / 1), +]; +const _scaleNames = [ + '-3', + '-2', + '-1', + '0', + '1', + '2', + '3', +]; + +const _scaleNumMult = [ + 1, + 1, + 1, + 1, + 1 + 1 / 4, + 1 + 1 / 2, + 1 + 1 / 1, +]; + +int displayScaleToIndex(double displayScale) { + final idx = _scales.indexWhere((elem) => elem > displayScale); + final currentScaleIdx = idx == -1 ? _scales.length - 1 : max(0, idx - 1); + return currentScaleIdx; +} + +double indexToDisplayScale(int scaleIdx) { + final displayScale = + _scales[max(min(scaleIdx, _scales.length - 1), 0)].toDouble(); + return displayScale; +} + +String indexToDisplayScaleName(int scaleIdx) => + _scaleNames[max(min(scaleIdx, _scales.length - 1), 0)]; + +final maxDisplayScaleIndex = _scales.length - 1; + +Widget buildSettingsPageDisplayScalePreferences( + {required BuildContext context, required ThemeSwitcherState switcher}) { + final preferencesRepository = PreferencesRepository.instance; + final themePreferences = preferencesRepository.value.themePreference; + + final currentScaleIdx = displayScaleToIndex(themePreferences.displayScale); + final currentScaleName = indexToDisplayScaleName(currentScaleIdx); + + return StyledSlider( + value: currentScaleIdx.toDouble(), + label: currentScaleName, + decoratorLabel: translate('settings_page.display_scale'), + max: _scales.length - 1.toDouble(), + divisions: _scales.length - 1, + leftWidget: const Icon(Icons.text_decrease), + rightWidget: const Icon(Icons.text_increase), + onChanged: (value) async { + final scaleIdx = value.toInt(); + final displayScale = indexToDisplayScale(scaleIdx); + final newThemePrefs = + themePreferences.copyWith(displayScale: displayScale); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + }); +} + +extension DisplayScaledNum on num { + double scaled(BuildContext context) { + final prefs = context.watch().state.asData?.value ?? + PreferencesRepository.instance.value; + final currentScaleIdx = + displayScaleToIndex(prefs.themePreference.displayScale); + return this * _scaleNumMult[currentScaleIdx]; + } +} + +extension DisplayScaledEdgeInsets on EdgeInsets { + EdgeInsets scaled(BuildContext context) { + final prefs = context.watch().state.asData?.value ?? + PreferencesRepository.instance.value; + final currentScaleIdx = + displayScaleToIndex(prefs.themePreference.displayScale); + return EdgeInsets.fromLTRB( + left * _scaleNumMult[currentScaleIdx], + top * _scaleNumMult[currentScaleIdx], + right * _scaleNumMult[currentScaleIdx], + bottom * _scaleNumMult[currentScaleIdx]); + } +} diff --git a/lib/theme/views/preferences/preferences.dart b/lib/theme/views/preferences/preferences.dart new file mode 100644 index 0000000..ddac4c1 --- /dev/null +++ b/lib/theme/views/preferences/preferences.dart @@ -0,0 +1,4 @@ +export 'brightness_preferences.dart'; +export 'color_preferences.dart'; +export 'display_scale_preferences.dart'; +export 'wallpaper_preferences.dart'; diff --git a/lib/theme/views/preferences/wallpaper_preferences.dart b/lib/theme/views/preferences/wallpaper_preferences.dart new file mode 100644 index 0000000..050e294 --- /dev/null +++ b/lib/theme/views/preferences/wallpaper_preferences.dart @@ -0,0 +1,25 @@ +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; + +Widget buildSettingsPageWallpaperPreferences( + {required BuildContext context, required ThemeSwitcherState switcher}) { + final preferencesRepository = PreferencesRepository.instance; + final themePreferences = preferencesRepository.value.themePreference; + + return StyledCheckbox( + value: themePreferences.enableWallpaper, + label: translate('settings_page.enable_wallpaper'), + onChanged: (value) async { + final newThemePrefs = themePreferences.copyWith(enableWallpaper: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + }); +} diff --git a/lib/theme/views/responsive.dart b/lib/theme/views/responsive.dart index 0182c9f..4ce42d8 100644 --- a/lib/theme/views/responsive.dart +++ b/lib/theme/views/responsive.dart @@ -3,6 +3,10 @@ import 'package:flutter/material.dart'; final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; final isiOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; +final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS; +final isWindows = !kIsWeb && defaultTargetPlatform == TargetPlatform.windows; +final isLinux = !kIsWeb && defaultTargetPlatform == TargetPlatform.linux; + final isMobile = !kIsWeb && (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.android); diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_widgets/styled_alert.dart similarity index 99% rename from lib/theme/views/styled_alert.dart rename to lib/theme/views/styled_widgets/styled_alert.dart index 1215c84..4dec616 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_widgets/styled_alert.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:rflutter_alert/rflutter_alert.dart'; -import '../theme.dart'; +import '../../theme.dart'; AlertStyle _alertStyle(BuildContext context) { final theme = Theme.of(context); @@ -186,6 +186,7 @@ Future showAlertWidgetModal( child: Text( translate('button.ok'), style: _buttonTextStyle(context), + softWrap: true, ), ) ], diff --git a/lib/theme/views/avatar_widget.dart b/lib/theme/views/styled_widgets/styled_avatar.dart similarity index 93% rename from lib/theme/views/avatar_widget.dart rename to lib/theme/views/styled_widgets/styled_avatar.dart index 42bea11..4d51a02 100644 --- a/lib/theme/views/avatar_widget.dart +++ b/lib/theme/views/styled_widgets/styled_avatar.dart @@ -2,10 +2,10 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import '../theme.dart'; +import '../../theme.dart'; -class AvatarWidget extends StatelessWidget { - const AvatarWidget({ +class StyledAvatar extends StatelessWidget { + const StyledAvatar({ required String name, required double size, required Color borderColor, @@ -44,7 +44,6 @@ class AvatarWidget extends StatelessWidget { width: 1 * (_size ~/ 32 + 1), strokeAlign: BorderSide.strokeAlignOutside)), child: AvatarImage( - //size: 32, backgroundImage: _imageProvider, backgroundColor: _scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders @@ -59,7 +58,7 @@ class AvatarWidget extends StatelessWidget { ? _backgroundColor : _foregroundColor, ), - ).fit().paddingAll(_size / 16))); + ).fit().paddingAll(_size / 16.scaled(context)))); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/views/styled_widgets/styled_checkbox.dart b/lib/theme/views/styled_widgets/styled_checkbox.dart new file mode 100644 index 0000000..505c547 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_checkbox.dart @@ -0,0 +1,61 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../views.dart'; + +const _kStyledCheckboxChanged = 'kStyledCheckboxChanged'; + +class StyledCheckbox extends StatelessWidget { + const StyledCheckbox( + {required bool value, + required String label, + String? decoratorLabel, + Future Function(bool)? onChanged, + super.key}) + : _value = value, + _onChanged = onChanged, + _label = label, + _decoratorLabel = decoratorLabel; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + + var textStyle = textTheme.labelLarge!; + if (_onChanged == null) { + textStyle = textStyle.copyWith(color: textStyle.color!.withAlpha(127)); + } + + Widget ctrl = Row(children: [ + Checkbox( + value: _value, + onChanged: _onChanged == null + ? null + : (value) { + if (value == null) { + return; + } + singleFuture((this, _kStyledCheckboxChanged), () async { + await _onChanged(value); + }); + }), + Text(_label, style: textStyle), + ]); + + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(4.scaled(context), 4.scaled(context), 4.scaled(context), + 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + + return ctrl; + } + + final String _label; + final String? _decoratorLabel; + final Future Function(bool)? _onChanged; + final bool _value; +} diff --git a/lib/theme/views/styled_dialog.dart b/lib/theme/views/styled_widgets/styled_dialog.dart similarity index 98% rename from lib/theme/views/styled_dialog.dart rename to lib/theme/views/styled_widgets/styled_dialog.dart index 75a0f6b..4106f1d 100644 --- a/lib/theme/views/styled_dialog.dart +++ b/lib/theme/views/styled_widgets/styled_dialog.dart @@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../theme.dart'; +import '../../theme.dart'; class StyledDialog extends StatelessWidget { const StyledDialog({required this.title, required this.child, super.key}); diff --git a/lib/theme/views/styled_widgets/styled_dropdown.dart b/lib/theme/views/styled_widgets/styled_dropdown.dart new file mode 100644 index 0000000..133c597 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_dropdown.dart @@ -0,0 +1,58 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../../models/models.dart'; +import '../views.dart'; + +const _kStyledDropdownChanged = 'kStyledDropdownChanged'; + +class StyledDropdown extends StatelessWidget { + const StyledDropdown( + {required List> items, + required T value, + String? decoratorLabel, + Future Function(T)? onChanged, + super.key}) + : _items = items, + _onChanged = onChanged, + _decoratorLabel = decoratorLabel, + _value = value; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scheme = theme.extension()!; + + Widget ctrl = DropdownButton( + isExpanded: true, + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0).scaled(context), + focusColor: theme.focusColor, + dropdownColor: scheme.primaryScale.elementBackground, + iconEnabledColor: scheme.primaryScale.appText, + iconDisabledColor: scheme.primaryScale.appText.withAlpha(127), + items: _items, + value: _value, + onChanged: _onChanged == null + ? null + : (value) { + if (value == null) { + return; + } + singleFuture((this, _kStyledDropdownChanged), () async { + await _onChanged(value); + }); + }); + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + return ctrl; + } + + final List> _items; + final String? _decoratorLabel; + final Future Function(T)? _onChanged; + final T _value; +} diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_widgets/styled_scaffold.dart similarity index 97% rename from lib/theme/views/styled_scaffold.dart rename to lib/theme/views/styled_widgets/styled_scaffold.dart index 4fc803f..82f27f5 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_widgets/styled_scaffold.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../theme.dart'; +import '../../theme.dart'; class StyledScaffold extends StatelessWidget { const StyledScaffold({required this.appBar, required this.body, super.key}); diff --git a/lib/theme/views/slider_tile.dart b/lib/theme/views/styled_widgets/styled_slide_tile.dart similarity index 93% rename from lib/theme/views/slider_tile.dart rename to lib/theme/views/styled_widgets/styled_slide_tile.dart index 8e5f178..1ae8596 100644 --- a/lib/theme/views/slider_tile.dart +++ b/lib/theme/views/styled_widgets/styled_slide_tile.dart @@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import '../theme.dart'; +import '../../theme.dart'; -class SliderTileAction { - const SliderTileAction({ +class SlideTileAction { + const SlideTileAction({ required this.actionScale, required this.onPressed, this.key, @@ -20,8 +20,8 @@ class SliderTileAction { final SlidableActionCallback? onPressed; } -class SliderTile extends StatelessWidget { - const SliderTile( +class StyledSlideTile extends StatelessWidget { + const StyledSlideTile( {required this.disabled, required this.selected, required this.tileScale, @@ -38,8 +38,8 @@ class SliderTile extends StatelessWidget { final bool disabled; final bool selected; final ScaleKind tileScale; - final List endActions; - final List startActions; + final List endActions; + final List startActions; final GestureTapCallback? onTap; final GestureTapCallback? onDoubleTap; final Widget? leading; @@ -54,8 +54,8 @@ class SliderTile extends StatelessWidget { ..add(DiagnosticsProperty('disabled', disabled)) ..add(DiagnosticsProperty('selected', selected)) ..add(DiagnosticsProperty('tileScale', tileScale)) - ..add(IterableProperty('endActions', endActions)) - ..add(IterableProperty('startActions', startActions)) + ..add(IterableProperty('endActions', endActions)) + ..add(IterableProperty('startActions', startActions)) ..add(ObjectFlagProperty.has('onTap', onTap)) ..add(DiagnosticsProperty('leading', leading)) ..add(StringProperty('title', title)) diff --git a/lib/theme/views/styled_widgets/styled_slider.dart b/lib/theme/views/styled_widgets/styled_slider.dart new file mode 100644 index 0000000..a0c7259 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_slider.dart @@ -0,0 +1,79 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../../models/models.dart'; +import '../views.dart'; + +const _kStyledSliderChanged = 'kStyledSliderChanged'; + +class StyledSlider extends StatelessWidget { + const StyledSlider( + {required double value, + String? label, + String? decoratorLabel, + Future Function(double)? onChanged, + Widget? leftWidget, + Widget? rightWidget, + double min = 0, + double max = 1, + int? divisions, + super.key}) + : _value = value, + _onChanged = onChanged, + _leftWidget = leftWidget, + _rightWidget = rightWidget, + _min = min, + _max = max, + _divisions = divisions, + _label = label, + _decoratorLabel = decoratorLabel; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + + Widget ctrl = Row(children: [ + if (_leftWidget != null) _leftWidget, + Slider( + activeColor: scale.scheme.primaryScale.border, + inactiveColor: scale.scheme.primaryScale.subtleBorder, + secondaryActiveColor: scale.scheme.secondaryScale.border, + value: _value, + min: _min, + max: _max, + divisions: _divisions, + label: _label, + thumbColor: scale.scheme.primaryScale.appText, + overlayColor: + WidgetStateColor.resolveWith((ws) => theme.focusColor), + onChanged: _onChanged == null + ? null + : (value) { + singleFuture((this, _kStyledSliderChanged), () async { + await _onChanged(value); + }); + }) + .expanded(), + if (_rightWidget != null) _rightWidget, + ]); + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(4.scaled(context), 4.scaled(context), 4.scaled(context), + 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + return ctrl; + } + + final String? _label; + final String? _decoratorLabel; + final Future Function(double)? _onChanged; + final double _value; + final Widget? _leftWidget; + final Widget? _rightWidget; + final double _min; + final double _max; + final int? _divisions; +} diff --git a/lib/theme/views/styled_widgets/styled_widgets.dart b/lib/theme/views/styled_widgets/styled_widgets.dart new file mode 100644 index 0000000..ae45d59 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_widgets.dart @@ -0,0 +1,8 @@ +export 'styled_alert.dart'; +export 'styled_avatar.dart'; +export 'styled_checkbox.dart'; +export 'styled_dialog.dart'; +export 'styled_dropdown.dart'; +export 'styled_scaffold.dart'; +export 'styled_slide_tile.dart'; +export 'styled_slider.dart'; diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index 88f4a4a..cffc364 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -1,16 +1,10 @@ -export 'avatar_widget.dart'; -export 'brightness_preferences.dart'; -export 'color_preferences.dart'; export 'enter_password.dart'; export 'enter_pin.dart'; export 'option_box.dart'; export 'pop_control.dart'; +export 'preferences/preferences.dart'; export 'recovery_key_widget.dart'; export 'responsive.dart'; export 'scanner_error_widget.dart'; -export 'slider_tile.dart'; -export 'styled_alert.dart'; -export 'styled_dialog.dart'; -export 'styled_scaffold.dart'; -export 'wallpaper_preferences.dart'; +export 'styled_widgets/styled_widgets.dart'; export 'widget_helpers.dart'; diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart deleted file mode 100644 index f9ae94c..0000000 --- a/lib/theme/views/wallpaper_preferences.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:animated_theme_switcher/animated_theme_switcher.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:flutter_translate/flutter_translate.dart'; - -import '../../settings/settings.dart'; -import '../models/models.dart'; - -const String formFieldEnableWallpaper = 'enable_wallpaper'; - -Widget buildSettingsPageWallpaperPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { - final preferencesRepository = PreferencesRepository.instance; - final themePreferences = preferencesRepository.value.themePreference; - final theme = Theme.of(context); - final textTheme = theme.textTheme; - - return FormBuilderCheckbox( - name: formFieldEnableWallpaper, - title: Text(translate('settings_page.enable_wallpaper'), - style: textTheme.labelMedium), - initialValue: themePreferences.enableWallpaper, - onChanged: (value) async { - if (value != null) { - final newThemePrefs = - themePreferences.copyWith(enableWallpaper: value); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); - - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - } - }); -} diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index d749a9c..51b452e 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -222,6 +222,7 @@ class _DeveloperPageState extends State { return Scaffold( backgroundColor: scale.primaryScale.border, appBar: DefaultAppBar( + context: context, title: Text(translate('developer.title')), leading: IconButton( icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText), diff --git a/lib/veilid_processor/views/signal_strength_meter.dart b/lib/veilid_processor/views/signal_strength_meter.dart index 5385bb1..44e3cb6 100644 --- a/lib/veilid_processor/views/signal_strength_meter.dart +++ b/lib/veilid_processor/views/signal_strength_meter.dart @@ -19,7 +19,7 @@ class SignalStrengthMeterWidget extends StatelessWidget { final theme = Theme.of(context); final scale = theme.extension()!; - const iconSize = 16.0; + final iconSize = 16.0.scaled(context); return BlocBuilder>(builder: (context, state) {