accessibility work

This commit is contained in:
Christien Rioux 2025-05-25 13:09:41 -04:00
parent be8014c97a
commit de691cd778
48 changed files with 862 additions and 551 deletions

View file

@ -276,6 +276,7 @@
"titlebar": "Settings", "titlebar": "Settings",
"color_theme": "Color Theme", "color_theme": "Color Theme",
"brightness_mode": "Brightness Mode", "brightness_mode": "Brightness Mode",
"display_scale": "Display Scale",
"display_beta_warning": "Display beta warning on startup", "display_beta_warning": "Display beta warning on startup",
"none": "None", "none": "None",
"in_app": "In-app", "in_app": "In-app",

View file

@ -6,54 +6,9 @@ PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_native_splash (2.4.3): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- GoogleDataTransport (10.1.0): - mobile_scanner (7.0.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):
- Flutter - Flutter
- GoogleMLKit/BarcodeScanning (~> 7.0.0) - FlutterMacOS
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- pasteboard (0.0.1): - pasteboard (0.0.1):
@ -63,7 +18,6 @@ PODS:
- FlutterMacOS - FlutterMacOS
- printing (1.0.0): - printing (1.0.0):
- Flutter - Flutter
- PromisesObjC (2.4.0)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@ -84,7 +38,7 @@ DEPENDENCIES:
- file_saver (from `.symlinks/plugins/file_saver/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - 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`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - 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`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- veilid (from `.symlinks/plugins/veilid/ios`) - veilid (from `.symlinks/plugins/veilid/ios`)
SPEC REPOS:
trunk:
- GoogleDataTransport
- GoogleMLKit
- GoogleToolboxForMac
- GoogleUtilities
- GTMSessionFetcher
- MLImage
- MLKitBarcodeScanning
- MLKitCommon
- MLKitVision
- nanopb
- PromisesObjC
EXTERNAL SOURCES: EXTERNAL SOURCES:
camera_avfoundation: camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios" :path: ".symlinks/plugins/camera_avfoundation/ios"
@ -120,7 +60,7 @@ EXTERNAL SOURCES:
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
mobile_scanner: mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/ios" :path: ".symlinks/plugins/mobile_scanner/darwin"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
pasteboard: pasteboard:
@ -143,26 +83,15 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/veilid/ios" :path: ".symlinks/plugins/veilid/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0

View file

@ -139,7 +139,6 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
02C44F9283ADDE9FAAA73512 /* [CP] Embed Pods Frameworks */, 02C44F9283ADDE9FAAA73512 /* [CP] Embed Pods Frameworks */,
61BE8A90522682C17620991D /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -232,23 +231,6 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 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 */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;

View file

@ -26,6 +26,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@ -43,6 +44,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"

View file

@ -73,15 +73,18 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
title: translate('edit_account_page.remove_account_confirm'), title: translate('edit_account_page.remove_account_confirm'),
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(translate('edit_account_page.remove_account_confirm_message')) Text(translate('edit_account_page.remove_account_confirm_message'))
.paddingLTRB(24, 24, 24, 0), .paddingLTRB(24.scaled(context), 24.scaled(context),
Text(translate('confirmation.are_you_sure')).paddingAll(8), 24.scaled(context), 0),
Text(translate('confirmation.are_you_sure'))
.paddingAll(8.scaled(context)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(false); Navigator.of(context).pop(false);
}, },
child: Row(mainAxisSize: MainAxisSize.min, children: [ 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) Text(translate('button.no')).paddingLTRB(0, 0, 4, 0)
])), ])),
ElevatedButton( ElevatedButton(
@ -89,10 +92,12 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Row(mainAxisSize: MainAxisSize.min, children: [
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Icon(Icons.check, size: 16.scaled(context))
Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) .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) { if (confirmed != null && confirmed) {
try { try {
@ -141,29 +146,36 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
title: translate('edit_account_page.destroy_account_confirm'), title: translate('edit_account_page.destroy_account_confirm'),
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(translate('edit_account_page.destroy_account_confirm_message')) 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( Text(translate(
'edit_account_page.destroy_account_confirm_message_details')) 'edit_account_page.destroy_account_confirm_message_details'))
.paddingLTRB(24, 24, 24, 0), .paddingLTRB(24.scaled(context), 24.scaled(context),
Text(translate('confirmation.are_you_sure')).paddingAll(8), 24.scaled(context), 0),
Text(translate('confirmation.are_you_sure'))
.paddingAll(24.scaled(context)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(false); Navigator.of(context).pop(false);
}, },
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Row(mainAxisSize: MainAxisSize.min, children: [
const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), Icon(Icons.cancel, size: 16.scaled(context))
Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) .paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.no'))
.paddingLTRB(0, 0, 4.scaled(context), 0)
])), ])),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Row(mainAxisSize: MainAxisSize.min, children: [
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Icon(Icons.check, size: 16.scaled(context))
Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) .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) { if (confirmed != null && confirmed) {
try { try {
@ -250,6 +262,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
return StyledScaffold( return StyledScaffold(
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text(translate('edit_account_page.titlebar')), title: Text(translate('edit_account_page.titlebar')),
leading: Navigator.canPop(context) leading: Navigator.canPop(context)
? IconButton( ? IconButton(
@ -277,6 +290,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
const SignalStrengthMeterWidget(), const SignalStrengthMeterWidget(),
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
onPressed: () async { onPressed: () async {
await GoRouterHelper(context).push('/settings'); await GoRouterHelper(context).push('/settings');

View file

@ -53,16 +53,16 @@ class EditProfileForm extends StatefulWidget {
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue)); ..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue));
} }
static const String formFieldName = 'name'; static const formFieldName = 'name';
static const String formFieldPronouns = 'pronouns'; static const formFieldPronouns = 'pronouns';
static const String formFieldAbout = 'about'; static const formFieldAbout = 'about';
static const String formFieldAvailability = 'availability'; static const formFieldAvailability = 'availability';
static const String formFieldFreeMessage = 'free_message'; static const formFieldFreeMessage = 'free_message';
static const String formFieldAwayMessage = 'away_message'; static const formFieldAwayMessage = 'away_message';
static const String formFieldBusyMessage = 'busy_message'; static const formFieldBusyMessage = 'busy_message';
static const String formFieldAvatar = 'avatar'; static const formFieldAvatar = 'avatar';
static const String formFieldAutoAway = 'auto_away'; static const formFieldAutoAway = 'auto_away';
static const String formFieldAutoAwayTimeout = 'auto_away_timeout'; static const formFieldAutoAwayTimeout = 'auto_away_timeout';
} }
class _EditProfileFormState extends State<EditProfileForm> { class _EditProfileFormState extends State<EditProfileForm> {
@ -98,6 +98,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
name: EditProfileForm.formFieldAvailability, name: EditProfileForm.formFieldAvailability,
initialValue: initialValue, initialValue: initialValue,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_availability'), labelText: translate('account.form_availability'),
hintText: translate('account.empty_busy_message')), hintText: translate('account.empty_busy_message')),
@ -110,7 +111,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
Text(x == proto.Availability.AVAILABILITY_OFFLINE Text(x == proto.Availability.AVAILABILITY_OFFLINE
? translate('availability.always_show_offline') ? translate('availability.always_show_offline')
: AvailabilityWidget.availabilityName(x)) : AvailabilityWidget.availabilityName(x))
.paddingLTRB(8, 0, 0, 0), .paddingLTRB(8.scaled(context), 0, 0, 0),
]))) ])))
.toList(), .toList(),
); );
@ -197,7 +198,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
children: [ children: [
Row(children: [ Row(children: [
const Spacer(), const Spacer(),
AvatarWidget( StyledAvatar(
name: _currentValueName, name: _currentValueName,
size: 128, size: 128,
borderColor: border, borderColor: border,
@ -218,6 +219,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_name'), labelText: translate('account.form_name'),
hintText: translate('account.empty_name')), hintText: translate('account.empty_name')),
@ -233,6 +235,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.pronouns, initialValue: _savedValue.pronouns,
maxLength: 64, maxLength: 64,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_pronouns'), labelText: translate('account.form_pronouns'),
hintText: translate('account.empty_pronouns')), hintText: translate('account.empty_pronouns')),
@ -245,6 +248,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
maxLines: 8, maxLines: 8,
minLines: 1, minLines: 1,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_about'), labelText: translate('account.form_about'),
hintText: translate('account.empty_about')), hintText: translate('account.empty_about')),
@ -256,6 +260,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.freeMessage, initialValue: _savedValue.freeMessage,
maxLength: 128, maxLength: 128,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_free_message'), labelText: translate('account.form_free_message'),
hintText: translate('account.empty_free_message')), hintText: translate('account.empty_free_message')),
@ -266,6 +271,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.awayMessage, initialValue: _savedValue.awayMessage,
maxLength: 128, maxLength: 128,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_away_message'), labelText: translate('account.form_away_message'),
hintText: translate('account.empty_away_message')), hintText: translate('account.empty_away_message')),
@ -276,6 +282,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.busyMessage, initialValue: _savedValue.busyMessage,
maxLength: 128, maxLength: 128,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_busy_message'), labelText: translate('account.form_busy_message'),
hintText: translate('account.empty_busy_message')), hintText: translate('account.empty_busy_message')),
@ -291,12 +298,13 @@ class _EditProfileFormState extends State<EditProfileForm> {
_currentValueAutoAway = v ?? false; _currentValueAutoAway = v ?? false;
}); });
}, },
).paddingLTRB(0, 0, 0, 16), ).paddingLTRB(0, 0, 0, 16.scaled(context)),
FormBuilderTextField( FormBuilderTextField(
name: EditProfileForm.formFieldAutoAwayTimeout, name: EditProfileForm.formFieldAutoAwayTimeout,
enabled: _currentValueAutoAway, enabled: _currentValueAutoAway,
initialValue: _savedValue.autoAwayTimeout.toString(), initialValue: _savedValue.autoAwayTimeout.toString(),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
labelText: translate('account.form_auto_away_timeout'), labelText: translate('account.form_auto_away_timeout'),
), ),
validator: FormBuilderValidators.positiveNumber(), validator: FormBuilderValidators.positiveNumber(),
@ -306,7 +314,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
const Spacer(), const Spacer(),
Text(widget.instructions).toCenter().flexible(flex: 6), Text(widget.instructions).toCenter().flexible(flex: 6),
const Spacer(), const Spacer(),
]).paddingSymmetric(vertical: 16), ]).paddingSymmetric(vertical: 16.scaled(context)),
Row(children: [ Row(children: [
const Spacer(), const Spacer(),
Builder(builder: (context) { Builder(builder: (context) {
@ -322,12 +330,12 @@ class _EditProfileFormState extends State<EditProfileForm> {
onPressed: (networkReady && _isModified) ? _doSubmit : null, onPressed: (networkReady && _isModified) ? _doSubmit : null,
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(networkReady ? Icons.check : Icons.hourglass_empty, Icon(networkReady ? Icons.check : Icons.hourglass_empty,
size: 16) size: 16.scaled(context))
.paddingLTRB(0, 0, 4, 0), .paddingLTRB(0, 0, 4.scaled(context), 0),
Text(networkReady Text(networkReady
? widget.submitText ? widget.submitText
: widget.submitDisabledText) : widget.submitDisabledText)
.paddingLTRB(0, 0, 4, 0) .paddingLTRB(0, 0, 4.scaled(context), 0)
]), ]),
); );
}), }),
@ -363,5 +371,5 @@ class _EditProfileFormState extends State<EditProfileForm> {
late AccountSpec _savedValue; late AccountSpec _savedValue;
late bool _currentValueAutoAway; late bool _currentValueAutoAway;
late String _currentValueName; late String _currentValueName;
bool _isModified = false; var _isModified = false;
} }

View file

@ -94,6 +94,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
return StyledScaffold( return StyledScaffold(
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text(translate('new_account_page.titlebar')), title: Text(translate('new_account_page.titlebar')),
leading: GoRouterHelper(context).canPop() leading: GoRouterHelper(context).canPop()
? IconButton( ? IconButton(
@ -111,6 +112,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
const SignalStrengthMeterWidget(), const SignalStrengthMeterWidget(),
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
onPressed: () async { onPressed: () async {
await GoRouterHelper(context).push('/settings'); await GoRouterHelper(context).push('/settings');

View file

@ -164,6 +164,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
return StyledScaffold( return StyledScaffold(
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text(translate('show_recovery_key_page.titlebar')), title: Text(translate('show_recovery_key_page.titlebar')),
actions: [ actions: [
const SignalStrengthMeterWidget(), const SignalStrengthMeterWidget(),

View file

@ -31,7 +31,7 @@ class VeilidChatApp extends StatelessWidget {
super.key, super.key,
}); });
static const String name = 'VeilidChat'; static const name = 'VeilidChat';
final ThemeData initialThemeData; final ThemeData initialThemeData;

View file

@ -355,6 +355,7 @@ class _VcComposerState extends State<VcComposerWidget> {
borderRadius: BorderRadius.all(Radius.circular( borderRadius: BorderRadius.all(Radius.circular(
8 * config.borderRadiusScale))), 8 * config.borderRadiusScale))),
hintText: widget.hintText, hintText: widget.hintText,
hintMaxLines: 1,
hintStyle: chatTheme.typography.bodyMedium.copyWith( hintStyle: chatTheme.typography.bodyMedium.copyWith(
color: widget.hintColor ?? color: widget.hintColor ??
chatTheme.colors.onSurface chatTheme.colors.onSurface

View file

@ -48,7 +48,7 @@ class ChatSingleContactItemWidget extends StatelessWidget {
selected: selected, selected: selected,
); );
final avatar = AvatarWidget( final avatar = StyledAvatar(
name: name, name: name,
size: 32, size: 32,
borderColor: scaleTheme.config.useVisualIndicators borderColor: scaleTheme.config.useVisualIndicators
@ -64,7 +64,7 @@ class ChatSingleContactItemWidget extends StatelessWidget {
textStyle: theme.textTheme.titleLarge!, textStyle: theme.textTheme.titleLarge!,
); );
return SliderTile( return StyledSlideTile(
key: ValueKey(_localConversationRecordKey), key: ValueKey(_localConversationRecordKey),
disabled: _disabled, disabled: _disabled,
selected: selected, selected: selected,
@ -82,7 +82,7 @@ class ChatSingleContactItemWidget extends StatelessWidget {
}); });
}, },
endActions: [ endActions: [
SliderTileAction( SlideTileAction(
//icon: Icons.delete, //icon: Icons.delete,
label: translate('button.delete'), label: translate('button.delete'),
actionScale: ScaleKind.tertiary, actionScale: ScaleKind.tertiary,

View file

@ -44,7 +44,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
title = contactInvitationRecord.message; title = contactInvitationRecord.message;
} }
return SliderTile( return StyledSlideTile(
key: ObjectKey(contactInvitationRecord), key: ObjectKey(contactInvitationRecord),
disabled: tileDisabled, disabled: tileDisabled,
selected: selected, selected: selected,
@ -67,7 +67,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
))); )));
}, },
endActions: [ endActions: [
SliderTileAction( SlideTileAction(
// icon: Icons.delete, // icon: Icons.delete,
label: translate('button.delete'), label: translate('button.delete'),
actionScale: ScaleKind.tertiary, actionScale: ScaleKind.tertiary,

View file

@ -1,37 +1,34 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
class AvailabilityWidget extends StatelessWidget { class AvailabilityWidget extends StatelessWidget {
const AvailabilityWidget( const AvailabilityWidget(
{required this.availability, {required this.availability,
required this.color, required this.color,
this.vertical = true, this.vertical = true,
this.size = 32,
super.key}); super.key});
static Widget availabilityIcon(proto.Availability availability, Color color, static Widget availabilityIcon(
{double size = 24}) { proto.Availability availability,
Color color,
) {
late final Widget icon; late final Widget icon;
switch (availability) { switch (availability) {
case proto.Availability.AVAILABILITY_AWAY: case proto.Availability.AVAILABILITY_AWAY:
icon = SvgPicture.asset('assets/images/toilet.svg', icon = SvgPicture.asset('assets/images/toilet.svg',
width: size,
height: size,
colorFilter: ColorFilter.mode(color, BlendMode.srcATop)); colorFilter: ColorFilter.mode(color, BlendMode.srcATop));
case proto.Availability.AVAILABILITY_BUSY: 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: 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: 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: case proto.Availability.AVAILABILITY_UNSPECIFIED:
icon = Icon(Icons.question_mark, size: size); icon = const Icon(Icons.question_mark, applyTextScaling: true);
} }
return icon; return icon;
} }
@ -59,23 +56,17 @@ class AvailabilityWidget extends StatelessWidget {
final textTheme = theme.textTheme; final textTheme = theme.textTheme;
final name = availabilityName(availability); final name = availabilityName(availability);
final icon = availabilityIcon(availability, color, size: size * 2 / 3); final icon = availabilityIcon(availability, color);
return vertical return vertical
? ConstrainedBox( ? Column(mainAxisSize: MainAxisSize.min, children: [
constraints: BoxConstraints.tightFor(width: size),
child: Column(mainAxisSize: MainAxisSize.min, children: [
icon, icon,
Text(name, style: textTheme.labelSmall!.copyWith(color: color)) Text(name, style: textTheme.labelSmall!.copyWith(color: color))
.fit(fit: BoxFit.scaleDown) ])
])) : Row(mainAxisSize: MainAxisSize.min, children: [
: ConstrainedBox(
constraints: BoxConstraints.tightFor(height: size),
child: Row(mainAxisSize: MainAxisSize.min, children: [
icon, icon,
Text(name, style: textTheme.labelLarge!.copyWith(color: color)) Text(' $name', style: textTheme.labelLarge!.copyWith(color: color))
.paddingLTRB(size / 4, 0, 0, 0) ]);
]));
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -83,7 +74,6 @@ class AvailabilityWidget extends StatelessWidget {
final proto.Availability availability; final proto.Availability availability;
final Color color; final Color color;
final bool vertical; final bool vertical;
final double size;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -92,7 +82,6 @@ class AvailabilityWidget extends StatelessWidget {
..add( ..add(
DiagnosticsProperty<proto.Availability>('availability', availability)) DiagnosticsProperty<proto.Availability>('availability', availability))
..add(DiagnosticsProperty<bool>('vertical', vertical)) ..add(DiagnosticsProperty<bool>('vertical', vertical))
..add(DoubleProperty('size', size))
..add(ColorProperty('color', color)); ..add(ColorProperty('color', color));
} }
} }

View file

@ -34,7 +34,7 @@ class ContactItemWidget extends StatelessWidget {
final title = _contact.displayName; final title = _contact.displayName;
final subtitle = _contact.profile.status; final subtitle = _contact.profile.status;
final avatar = AvatarWidget( final avatar = StyledAvatar(
name: name, name: name,
size: 34, size: 34,
borderColor: _disabled borderColor: _disabled
@ -49,7 +49,7 @@ class ContactItemWidget extends StatelessWidget {
textStyle: theme.textTheme.titleLarge!, textStyle: theme.textTheme.titleLarge!,
); );
return SliderTile( return StyledSlideTile(
key: ObjectKey(_contact), key: ObjectKey(_contact),
disabled: _disabled, disabled: _disabled,
selected: _selected, selected: _selected,
@ -69,7 +69,7 @@ class ContactItemWidget extends StatelessWidget {
}), }),
startActions: [ startActions: [
if (_onDoubleTap != null) if (_onDoubleTap != null)
SliderTileAction( SlideTileAction(
//icon: Icons.edit, //icon: Icons.edit,
label: translate('button.chat'), label: translate('button.chat'),
actionScale: ScaleKind.secondary, actionScale: ScaleKind.secondary,
@ -81,7 +81,7 @@ class ContactItemWidget extends StatelessWidget {
], ],
endActions: [ endActions: [
if (_onTap != null) if (_onTap != null)
SliderTileAction( SlideTileAction(
//icon: Icons.edit, //icon: Icons.edit,
label: translate('button.edit'), label: translate('button.edit'),
actionScale: ScaleKind.secondary, actionScale: ScaleKind.secondary,
@ -91,7 +91,7 @@ class ContactItemWidget extends StatelessWidget {
}), }),
), ),
if (_onDelete != null) if (_onDelete != null)
SliderTileAction( SlideTileAction(
//icon: Icons.delete, //icon: Icons.delete,
label: translate('button.delete'), label: translate('button.delete'),
actionScale: ScaleKind.tertiary, actionScale: ScaleKind.tertiary,

View file

@ -40,6 +40,7 @@ class _ContactsPageState extends State<ContactsPage> {
return StyledScaffold( return StyledScaffold(
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text( title: Text(
!enableSplit && enableRight !enableSplit && enableRight
? translate('contacts_dialog.edit_contact') ? translate('contacts_dialog.edit_contact')

View file

@ -116,7 +116,7 @@ class _EditContactFormState extends State<EditContactForm> {
children: [ children: [
Row(children: [ Row(children: [
const Spacer(), const Spacer(),
AvatarWidget( StyledAvatar(
name: _currentValueNickname.isNotEmpty name: _currentValueNickname.isNotEmpty
? _currentValueNickname ? _currentValueNickname
: widget.contact.profile.name, : widget.contact.profile.name,

View file

@ -32,6 +32,14 @@ class DeveloperPageIntent extends Intent {
const DeveloperPageIntent(); const DeveloperPageIntent();
} }
class DisplayScaleUpIntent extends Intent {
const DisplayScaleUpIntent();
}
class DisplayScaleDownIntent extends Intent {
const DisplayScaleDownIntent();
}
class KeyboardShortcuts extends StatelessWidget { class KeyboardShortcuts extends StatelessWidget {
const KeyboardShortcuts({required this.child, super.key}); 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 { singleFuture(this, () async {
final prefs = PreferencesRepository.instance.value; final prefs = PreferencesRepository.instance.value;
@ -79,7 +87,7 @@ class KeyboardShortcuts extends StatelessWidget {
}); });
} }
void changeColor(BuildContext context) { void _changeColor(BuildContext context) {
singleFuture(this, () async { singleFuture(this, () async {
final prefs = PreferencesRepository.instance.value; final prefs = PreferencesRepository.instance.value;
final oldColor = prefs.themePreference.colorPreference; 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) { void _attachDetach(BuildContext context) {
singleFuture(this, () async { singleFuture(this, () async {
if (ProcessorRepository.instance.processorConnectionState.isAttached) { if (ProcessorRepository.instance.processorConnectionState.isAttached) {
@ -125,44 +181,88 @@ class KeyboardShortcuts extends StatelessWidget {
@override @override
Widget build(BuildContext context) => ThemeSwitcher( Widget build(BuildContext context) => ThemeSwitcher(
builder: (context) => Shortcuts( builder: (context) => Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{ shortcuts: <ShortcutActivator, Intent>{
SingleActivator( ////////////////////////// Reload Theme
const SingleActivator(
LogicalKeyboardKey.keyR, LogicalKeyboardKey.keyR,
control: true, control: true,
alt: true, alt: true,
): ReloadThemeIntent(), ): const ReloadThemeIntent(),
SingleActivator( ////////////////////////// Switch Brightness
const SingleActivator(
LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyB,
control: true, control: true,
alt: true, alt: true,
): ChangeBrightnessIntent(), ): const ChangeBrightnessIntent(),
SingleActivator( ////////////////////////// Change Color
const SingleActivator(
LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyC,
control: true, control: true,
alt: true, alt: true,
): ChangeColorIntent(), ): const ChangeColorIntent(),
SingleActivator( ////////////////////////// Attach/Detach
if (kIsDebugMode)
const SingleActivator(
LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyA,
control: true, control: true,
alt: true, alt: true,
): AttachDetachIntent(), ): const AttachDetachIntent(),
SingleActivator( ////////////////////////// Show Developer Page
const SingleActivator(
LogicalKeyboardKey.keyD, LogicalKeyboardKey.keyD,
control: true, control: true,
alt: 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: <Type, Action<Intent>>{ child: Actions(actions: <Type, Action<Intent>>{
ReloadThemeIntent: CallbackAction<ReloadThemeIntent>( ReloadThemeIntent: CallbackAction<ReloadThemeIntent>(
onInvoke: (intent) => reloadTheme(context)), onInvoke: (intent) => reloadTheme(context)),
ChangeBrightnessIntent: CallbackAction<ChangeBrightnessIntent>( ChangeBrightnessIntent: CallbackAction<ChangeBrightnessIntent>(
onInvoke: (intent) => changeBrightness(context)), onInvoke: (intent) => _changeBrightness(context)),
ChangeColorIntent: CallbackAction<ChangeColorIntent>( ChangeColorIntent: CallbackAction<ChangeColorIntent>(
onInvoke: (intent) => changeColor(context)), onInvoke: (intent) => _changeColor(context)),
AttachDetachIntent: CallbackAction<AttachDetachIntent>( AttachDetachIntent: CallbackAction<AttachDetachIntent>(
onInvoke: (intent) => _attachDetach(context)), onInvoke: (intent) => _attachDetach(context)),
DeveloperPageIntent: CallbackAction<DeveloperPageIntent>( DeveloperPageIntent: CallbackAction<DeveloperPageIntent>(
onInvoke: (intent) => _developerPage(context)), onInvoke: (intent) => _developerPage(context)),
DisplayScaleUpIntent: CallbackAction<DisplayScaleUpIntent>(
onInvoke: (intent) => _displayScaleUp(context)),
DisplayScaleDownIntent: CallbackAction<DisplayScaleDownIntent>(
onInvoke: (intent) => _displayScaleDown(context)),
}, child: Focus(autofocus: true, child: child)))); }, child: Focus(autofocus: true, child: child))));
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////

View file

@ -2,21 +2,25 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../theme/theme.dart';
class DefaultAppBar extends AppBar { class DefaultAppBar extends AppBar {
DefaultAppBar( DefaultAppBar(
{super.title, {required BuildContext context,
super.title,
super.flexibleSpace, super.flexibleSpace,
super.key, super.key,
Widget? leading, Widget? leading,
super.actions}) super.actions})
: super( : super(
toolbarHeight: 40.scaled(context),
leading: leading ?? leading: leading ??
Container( Container(
margin: const EdgeInsets.all(4), margin: const EdgeInsets.all(4).scaled(context),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withAlpha(32), color: Colors.black.withAlpha(32),
shape: BoxShape.circle), shape: BoxShape.circle),
child: child: SvgPicture.asset('assets/images/vlogo.svg',
SvgPicture.asset('assets/images/vlogo.svg', height: 24) height: 24.scaled(context))
.paddingAll(4))); .paddingAll(4)));
} }

View file

@ -95,9 +95,9 @@ class _DrawerMenuState extends State<DrawerMenu> {
activeBorder = scale.primary; activeBorder = scale.primary;
} }
final avatar = AvatarWidget( final avatar = StyledAvatar(
name: name, name: name,
size: 34, size: 34.scaled(context),
borderColor: border, borderColor: border,
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText, foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
backgroundColor: loggedIn ? scale.primary : scale.elementBackground, backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
@ -107,7 +107,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
return AnimatedPadding( return AnimatedPadding(
padding: EdgeInsets.fromLTRB(selected ? 0 : 8, selected ? 0 : 2, 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), duration: const Duration(milliseconds: 50),
child: MenuItemWidget( child: MenuItemWidget(
title: name, title: name,
@ -144,7 +145,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators) (scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
? null ? null
: activeBorder, : activeBorder,
minHeight: 48, minHeight: 48.scaled(context),
)); ));
} }
@ -196,7 +197,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
color: scaleScheme.errorScale.subtleBorder, color: scaleScheme.errorScale.subtleBorder,
borderRadius: 12 * scaleConfig.borderRadiusScale), borderRadius: 12 * scaleConfig.borderRadiusScale),
); );
loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8)); loggedInAccounts
.add(loggedInAccount.paddingLTRB(0, 0, 0, 8.scaled(context)));
} else { } else {
// Account is not logged in // Account is not logged in
final scale = theme.extension<ScaleScheme>()!.grayScale; final scale = theme.extension<ScaleScheme>()!.grayScale;
@ -246,8 +248,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
} }
return IconButton( return IconButton(
icon: icon, icon: icon,
padding: const EdgeInsets.all(12),
color: border, color: border,
constraints: const BoxConstraints.expand(height: 48, width: 48),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) { backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) { if (states.contains(WidgetState.hovered)) {
@ -286,7 +288,10 @@ class _DrawerMenuState extends State<DrawerMenu> {
final scale = scaleScheme.scale(_scaleKind); final scale = scaleScheme.scale(_scaleKind);
final settingsButton = _getButton( final settingsButton = _getButton(
icon: const Icon(Icons.settings), icon: const Icon(
Icons.settings,
applyTextScaling: true,
),
tooltip: translate('menu.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
scale: scale, scale: scale,
scaleConfig: scaleConfig, scaleConfig: scaleConfig,
@ -295,7 +300,10 @@ class _DrawerMenuState extends State<DrawerMenu> {
}).paddingLTRB(0, 0, 16, 0); }).paddingLTRB(0, 0, 16, 0);
final addButton = _getButton( final addButton = _getButton(
icon: const Icon(Icons.add), icon: const Icon(
Icons.add,
applyTextScaling: true,
),
tooltip: translate('menu.add_account_tooltip'), tooltip: translate('menu.add_account_tooltip'),
scale: scale, scale: scale,
scaleConfig: scaleConfig, scaleConfig: scaleConfig,
@ -364,7 +372,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
// : null) // : null)
// .paddingLTRB(0, 0, 16, 0), // .paddingLTRB(0, 0, 16, 0),
GestureDetector( GestureDetector(
onLongPress: () async { onLongPress: () {
context context
.findAncestorWidgetOfExactType<KeyboardShortcuts>()! .findAncestorWidgetOfExactType<KeyboardShortcuts>()!
.reloadTheme(context); .reloadTheme(context);

View file

@ -2,6 +2,8 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../theme/views/preferences/preferences.dart';
class MenuItemWidget extends StatelessWidget { class MenuItemWidget extends StatelessWidget {
const MenuItemWidget({ const MenuItemWidget({
required this.title, required this.title,
@ -81,7 +83,7 @@ class MenuItemWidget extends StatelessWidget {
hoverColor: footerButtonIconHoverColor, hoverColor: footerButtonIconHoverColor,
icon: Icon( icon: Icon(
footerButtonIcon, footerButtonIcon,
size: 24, size: 24.scaled(context),
), ),
onPressed: footerCallback), onPressed: footerCallback),
], ],

View file

@ -28,12 +28,16 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!; final scaleConfig = theme.extension<ScaleConfig>()!;
return IconButton( return AspectRatio(
icon: const Icon(Icons.menu), aspectRatio: 1,
child: IconButton(
icon: const Icon(
Icons.menu,
applyTextScaling: true,
),
color: scaleConfig.preferBorders color: scaleConfig.preferBorders
? scale.primaryScale.border ? scale.primaryScale.border
: scale.primaryScale.borderText, : scale.primaryScale.borderText,
constraints: const BoxConstraints.expand(height: 40, width: 40),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStateProperty.all( backgroundColor: WidgetStateProperty.all(
scaleConfig.preferBorders scaleConfig.preferBorders
@ -49,26 +53,30 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
? scale.primaryScale.border ? scale.primaryScale.border
: scale.primaryScale.borderText, : scale.primaryScale.borderText,
width: 2), width: 2),
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(
Radius.circular(8 * scaleConfig.borderRadiusScale))), 8 * scaleConfig.borderRadiusScale))),
)), )),
tooltip: translate('menu.accounts_menu_tooltip'), tooltip: translate('menu.accounts_menu_tooltip'),
onPressed: () async { onPressed: () async {
final ctrl = context.read<ZoomDrawerController>(); final ctrl = context.read<ZoomDrawerController>();
await ctrl.toggle?.call(); await ctrl.toggle?.call();
}); }));
}); });
Widget buildContactsButton() => Builder(builder: (context) { Widget buildContactsButton() => Builder(builder: (context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!; final scaleConfig = theme.extension<ScaleConfig>()!;
return IconButton( return AspectRatio(
icon: const Icon(Icons.contacts), aspectRatio: 1,
child: IconButton(
icon: const Icon(
Icons.contacts,
applyTextScaling: true,
),
color: scaleConfig.preferBorders color: scaleConfig.preferBorders
? scale.primaryScale.border ? scale.primaryScale.border
: scale.primaryScale.borderText, : scale.primaryScale.borderText,
constraints: const BoxConstraints.expand(height: 40, width: 40),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStateProperty.all( backgroundColor: WidgetStateProperty.all(
scaleConfig.preferBorders scaleConfig.preferBorders
@ -84,8 +92,8 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
? scale.primaryScale.border ? scale.primaryScale.border
: scale.primaryScale.borderText, : scale.primaryScale.borderText,
width: 2), width: 2),
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(
Radius.circular(8 * scaleConfig.borderRadiusScale))), 8 * scaleConfig.borderRadiusScale))),
)), )),
tooltip: translate('menu.contacts_tooltip'), tooltip: translate('menu.contacts_tooltip'),
onPressed: () async { onPressed: () async {
@ -94,7 +102,7 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
builder: (_) => const ContactsPage(), builder: (_) => const ContactsPage(),
), ),
); );
}); }));
}); });
Widget buildLeftPane(BuildContext context) => Builder( Widget buildLeftPane(BuildContext context) => Builder(
@ -112,14 +120,17 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
? scale.primaryScale.subtleBackground ? scale.primaryScale.subtleBackground
: scale.primaryScale.subtleBorder, : scale.primaryScale.subtleBorder,
child: Column(children: <Widget>[ child: Column(children: <Widget>[
Row(children: [ IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildMenuButton().paddingLTRB(0, 0, 8, 0), buildMenuButton().paddingLTRB(0, 0, 8, 0),
ProfileWidget( ProfileWidget(
profile: profile, profile: profile,
showPronouns: false, showPronouns: false,
).expanded(), ).expanded(),
buildContactsButton().paddingLTRB(8, 0, 0, 0), buildContactsButton().paddingLTRB(8, 0, 0, 0),
]).paddingAll(8), ])).paddingAll(8),
const ChatListWidget().expanded() const ChatListWidget().expanded()
])); ]));
}))); })));

View file

@ -1,24 +1,13 @@
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../settings/settings.dart'; import '../../settings/settings.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../notifications.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( Widget buildSettingsPageNotificationPreferences(
{required BuildContext context, required void Function() onChanged}) { {required BuildContext context}) {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!; final scaleConfig = theme.extension<ScaleConfig>()!;
@ -33,7 +22,6 @@ Widget buildSettingsPageNotificationPreferences(
final newPrefs = preferencesRepository.value final newPrefs = preferencesRepository.value
.copyWith(notificationsPreference: newNotificationsPreference); .copyWith(notificationsPreference: newNotificationsPreference);
await preferencesRepository.set(newPrefs); await preferencesRepository.set(newPrefs);
onChanged();
} }
List<DropdownMenuItem<NotificationMode>> notificationModeItems() { List<DropdownMenuItem<NotificationMode>> notificationModeItems() {
@ -127,66 +115,45 @@ Widget buildSettingsPageNotificationPreferences(
), ),
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
// Display Beta Warning // Display Beta Warning
FormBuilderCheckbox( StyledCheckbox(
name: formFieldDisplayBetaWarning, label: translate('settings_page.display_beta_warning'),
title: Text(translate('settings_page.display_beta_warning'), value: notificationsPreference.displayBetaWarning,
style: textTheme.labelMedium),
initialValue: notificationsPreference.displayBetaWarning,
onChanged: (value) async { onChanged: (value) async {
if (value == null) {
return;
}
final newNotificationsPreference = final newNotificationsPreference =
notificationsPreference.copyWith(displayBetaWarning: value); notificationsPreference.copyWith(displayBetaWarning: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}), }),
// Enable Badge // Enable Badge
FormBuilderCheckbox( StyledCheckbox(
name: formFieldEnableBadge, label: translate('settings_page.enable_badge'),
title: Text(translate('settings_page.enable_badge'), value: notificationsPreference.enableBadge,
style: textTheme.labelMedium),
initialValue: notificationsPreference.enableBadge,
onChanged: (value) async { onChanged: (value) async {
if (value == null) {
return;
}
final newNotificationsPreference = final newNotificationsPreference =
notificationsPreference.copyWith(enableBadge: value); notificationsPreference.copyWith(enableBadge: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}), }),
// Enable Notifications // Enable Notifications
FormBuilderCheckbox( StyledCheckbox(
name: formFieldEnableNotifications, label: translate('settings_page.enable_notifications'),
title: Text(translate('settings_page.enable_notifications'), value: notificationsPreference.enableNotifications,
style: textTheme.labelMedium),
initialValue: notificationsPreference.enableNotifications,
onChanged: (value) async { onChanged: (value) async {
if (value == null) {
return;
}
final newNotificationsPreference = final newNotificationsPreference =
notificationsPreference.copyWith(enableNotifications: value); notificationsPreference.copyWith(enableNotifications: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}), }),
StyledDropdown<MessageNotificationContent>(
FormBuilderDropdown( items: messageNotificationContentItems(),
name: formFieldMessageNotificationContent, value: notificationsPreference.messageNotificationContent,
isDense: false, decoratorLabel: translate('settings_page.message_notification_content'),
decoration: InputDecoration( onChanged: !notificationsPreference.enableNotifications
labelText: translate('settings_page.message_notification_content')), ? null
enabled: notificationsPreference.enableNotifications, : (value) async {
initialValue: notificationsPreference.messageNotificationContent, final newNotificationsPreference = notificationsPreference
onChanged: (value) async { .copyWith(messageNotificationContent: value);
if (value == null) {
return;
}
final newNotificationsPreference = notificationsPreference.copyWith(
messageNotificationContent: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}, },
items: messageNotificationContentItems(), ).paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)),
).paddingLTRB(0, 4, 0, 4),
// Notifications // Notifications
Table( Table(
@ -199,21 +166,21 @@ Widget buildSettingsPageNotificationPreferences(
color: scale.primaryScale.border, color: scale.primaryScale.border,
decorationColor: scale.primaryScale.border, decorationColor: scale.primaryScale.border,
decoration: TextDecoration.underline)) decoration: TextDecoration.underline))
.paddingAll(8), .paddingAll(8.scaled(context)),
Text(translate('settings_page.delivery'), Text(translate('settings_page.delivery'),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: textTheme.titleMedium!.copyWith( style: textTheme.titleMedium!.copyWith(
color: scale.primaryScale.border, color: scale.primaryScale.border,
decorationColor: scale.primaryScale.border, decorationColor: scale.primaryScale.border,
decoration: TextDecoration.underline)) decoration: TextDecoration.underline))
.paddingAll(8), .paddingAll(8.scaled(context)),
Text(translate('settings_page.sound'), Text(translate('settings_page.sound'),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: textTheme.titleMedium!.copyWith( style: textTheme.titleMedium!.copyWith(
color: scale.primaryScale.border, color: scale.primaryScale.border,
decorationColor: scale.primaryScale.border, decorationColor: scale.primaryScale.border,
decoration: TextDecoration.underline)) decoration: TextDecoration.underline))
.paddingAll(8), .paddingAll(8.scaled(context)),
]), ]),
TableRow(children: [ TableRow(children: [
// Invitation accepted // Invitation accepted
@ -221,36 +188,31 @@ Widget buildSettingsPageNotificationPreferences(
textAlign: TextAlign.right, textAlign: TextAlign.right,
translate('settings_page.invitation_accepted')) translate('settings_page.invitation_accepted'))
.paddingAll(8), .paddingAll(8),
FormBuilderDropdown( StyledDropdown<NotificationMode>(
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);
},
items: notificationModeItems(), items: notificationModeItems(),
).paddingAll(4), value: notificationsPreference.onInvitationAcceptedMode,
FormBuilderDropdown( onChanged: !notificationsPreference.enableNotifications
name: formFieldInvitationAcceptSound, ? null
isDense: false, : (value) async {
enabled: notificationsPreference.enableNotifications, final newNotificationsPreference =
initialValue: notificationsPreference.onInvitationAcceptedSound, notificationsPreference.copyWith(
onChanged: (value) async { onInvitationAcceptedMode: value);
if (value == null) {
return;
}
final newNotificationsPreference = notificationsPreference
.copyWith(onInvitationAcceptedSound: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}, },
).paddingAll(4.scaled(context)),
StyledDropdown<SoundEffect>(
items: soundEffectItems(), 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 // Message received
TableRow(children: [ TableRow(children: [
@ -258,36 +220,31 @@ Widget buildSettingsPageNotificationPreferences(
textAlign: TextAlign.right, textAlign: TextAlign.right,
translate('settings_page.message_received')) translate('settings_page.message_received'))
.paddingAll(8), .paddingAll(8),
FormBuilderDropdown( StyledDropdown<NotificationMode>(
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);
},
items: notificationModeItems(), items: notificationModeItems(),
).paddingAll(4), value: notificationsPreference.onMessageReceivedMode,
FormBuilderDropdown( onChanged: !notificationsPreference.enableNotifications
name: formFieldMessageReceivedSound, ? null
isDense: false, : (value) async {
enabled: notificationsPreference.enableNotifications, final newNotificationsPreference =
initialValue: notificationsPreference.onMessageReceivedSound, notificationsPreference.copyWith(
onChanged: (value) async { onMessageReceivedMode: value);
if (value == null) {
return;
}
final newNotificationsPreference = notificationsPreference
.copyWith(onMessageReceivedSound: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}, },
).paddingAll(4),
StyledDropdown<SoundEffect>(
items: soundEffectItems(), 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 // Message sent
@ -295,25 +252,23 @@ Widget buildSettingsPageNotificationPreferences(
Text( Text(
textAlign: TextAlign.right, textAlign: TextAlign.right,
translate('settings_page.message_sent')) translate('settings_page.message_sent'))
.paddingAll(8), .paddingAll(8.scaled(context)),
const SizedBox.shrink(), const SizedBox.shrink(),
FormBuilderDropdown( StyledDropdown<SoundEffect>(
name: formFieldMessageSentSound, items: soundEffectItems(),
isDense: false, value: notificationsPreference.onMessageSentSound,
enabled: notificationsPreference.enableNotifications, onChanged: !notificationsPreference.enableNotifications
initialValue: notificationsPreference.onMessageSentSound, ? null
onChanged: (value) async { : (value) async {
if (value == null) { final newNotificationsPreference =
return; notificationsPreference.copyWith(
} onMessageSentSound: value);
final newNotificationsPreference = notificationsPreference
.copyWith(onMessageSentSound: value);
await updatePreferences(newNotificationsPreference); await updatePreferences(newNotificationsPreference);
}, },
items: soundEffectItems(), ).paddingLTRB(
).paddingLTRB(4, 4, 0, 4) 4.scaled(context), 4.scaled(context), 0, 4.scaled(context))
]), ]),
]) ])
]).paddingAll(8), ]).paddingAll(8.scaled(context)),
); );
} }

View file

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../../keyboard_shortcuts.dart'; import '../../keyboard_shortcuts.dart';
import '../../notifications/notifications.dart'; import '../../notifications/notifications.dart';
import '../../settings/settings.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
class RouterShell extends StatelessWidget { class RouterShell extends StatelessWidget {
@ -10,7 +12,13 @@ class RouterShell extends StatelessWidget {
@override @override
Widget build(BuildContext context) => PopControl( Widget build(BuildContext context) => PopControl(
dismissible: false, dismissible: false,
child: NotificationsWidget(child: KeyboardShortcuts(child: _child))); child: AsyncBlocBuilder<PreferencesCubit, Preferences>(
builder: (context, state) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler:
TextScaler.linear(state.themePreference.displayScale)),
child: NotificationsWidget(
child: KeyboardShortcuts(child: _child)))));
final Widget _child; final Widget _child;
} }

View file

@ -20,7 +20,7 @@ sealed class LockPreference with _$LockPreference {
factory LockPreference.fromJson(dynamic json) => factory LockPreference.fromJson(dynamic json) =>
_$LockPreferenceFromJson(json as Map<String, dynamic>); _$LockPreferenceFromJson(json as Map<String, dynamic>);
static const LockPreference defaults = LockPreference(); static const defaults = LockPreference();
} }
// Theme supports multiple translations // Theme supports multiple translations
@ -49,5 +49,5 @@ sealed class Preferences with _$Preferences {
factory Preferences.fromJson(dynamic json) => factory Preferences.fromJson(dynamic json) =>
_$PreferencesFromJson(json as Map<String, dynamic>); _$PreferencesFromJson(json as Map<String, dynamic>);
static const Preferences defaults = Preferences(); static const defaults = Preferences();
} }

View file

@ -1,7 +1,6 @@
import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:animated_theme_switcher/animated_theme_switcher.dart';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -11,29 +10,16 @@ import '../theme/theme.dart';
import '../veilid_processor/veilid_processor.dart'; import '../veilid_processor/veilid_processor.dart';
import 'settings.dart'; import 'settings.dart';
class SettingsPage extends StatefulWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});
@override
SettingsPageState createState() => SettingsPageState();
}
class SettingsPageState extends State<SettingsPage> {
final _formKey = GlobalKey<FormBuilderState>();
static const String formFieldTheme = 'theme';
static const String formFieldBrightness = 'brightness';
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) =>
AsyncBlocBuilder<PreferencesCubit, Preferences>( AsyncBlocBuilder<PreferencesCubit, Preferences>(
builder: (context, state) => ThemeSwitcher.withTheme( builder: (context, state) => ThemeSwitcher.withTheme(
builder: (_, switcher, theme) => StyledScaffold( builder: (_, switcher, theme) => StyledScaffold(
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text(translate('settings_page.titlebar')), title: Text(translate('settings_page.titlebar')),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
@ -43,30 +29,33 @@ class SettingsPageState extends State<SettingsPage> {
const SignalStrengthMeterWidget() const SignalStrengthMeterWidget()
.paddingLTRB(16, 0, 16, 0), .paddingLTRB(16, 0, 16, 0),
]), ]),
body: ThemeSwitchingArea( body: ListView(
child: FormBuilder( padding: const EdgeInsets.all(8).scaled(context),
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(8),
children: [ children: [
buildSettingsPageColorPreferences( buildSettingsPageColorPreferences(
context: context, context: context,
switcher: switcher, switcher: switcher,
onChanged: () => setState(() {})) ),
.paddingLTRB(0, 8, 0, 0),
buildSettingsPageBrightnessPreferences( buildSettingsPageBrightnessPreferences(
context: context, context: context,
switcher: switcher, switcher: switcher,
onChanged: () => setState(() {})), ),
buildSettingsPageDisplayScalePreferences(
context: context,
switcher: switcher,
),
buildSettingsPageWallpaperPreferences( buildSettingsPageWallpaperPreferences(
context: context, context: context,
switcher: switcher, switcher: switcher,
onChanged: () => setState(() {})), ),
buildSettingsPageNotificationPreferences( buildSettingsPageNotificationPreferences(
context: context, context: context,
onChanged: () => setState(() {})),
].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(),
), ),
).paddingSymmetric(horizontal: 8, vertical: 8), ]
)))); .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)),
));
} }

View file

@ -308,6 +308,13 @@ ThemeData contrastGenerator({
side: elementBorderWidgetStateProperty(), side: elementBorderWidgetStateProperty(),
backgroundColor: elementBackgroundWidgetStateProperty())); 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( final themeData = baseThemeData.copyWith(
// chipTheme: baseThemeData.chipTheme.copyWith( // chipTheme: baseThemeData.chipTheme.copyWith(
// backgroundColor: scaleScheme.primaryScale.elementBackground, // backgroundColor: scaleScheme.primaryScale.elementBackground,
@ -316,6 +323,7 @@ ThemeData contrastGenerator({
// checkmarkColor: scaleScheme.primaryScale.border, // checkmarkColor: scaleScheme.primaryScale.border,
// side: BorderSide(color: scaleScheme.primaryScale.border)), // side: BorderSide(color: scaleScheme.primaryScale.border)),
elevatedButtonTheme: elevatedButtonTheme, elevatedButtonTheme: elevatedButtonTheme,
sliderTheme: sliderTheme,
textSelectionTheme: TextSelectionThemeData( textSelectionTheme: TextSelectionThemeData(
cursorColor: scheme.primaryScale.appText, cursorColor: scheme.primaryScale.appText,
selectionColor: scheme.primaryScale.appText.withAlpha(0x7F), selectionColor: scheme.primaryScale.appText.withAlpha(0x7F),

View file

@ -132,6 +132,13 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
iconColor: elementColorWidgetStateProperty(), 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( final themeData = baseThemeData.copyWith(
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
thumbColor: WidgetStateProperty.resolveWith((states) { thumbColor: WidgetStateProperty.resolveWith((states) {
@ -183,6 +190,7 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
elevatedButtonTheme: elevatedButtonTheme, elevatedButtonTheme: elevatedButtonTheme,
inputDecorationTheme: inputDecorationTheme:
ScaleInputDecoratorTheme(scheme, config, textTheme), ScaleInputDecoratorTheme(scheme, config, textTheme),
sliderTheme: sliderTheme,
extensions: <ThemeExtension<dynamic>>[scheme, config, this]); extensions: <ThemeExtension<dynamic>>[scheme, config, this]);
return themeData; return themeData;

View file

@ -61,7 +61,7 @@ sealed class ThemePreferences with _$ThemePreferences {
factory ThemePreferences.fromJson(dynamic json) => factory ThemePreferences.fromJson(dynamic json) =>
_$ThemePreferencesFromJson(json as Map<String, dynamic>); _$ThemePreferencesFromJson(json as Map<String, dynamic>);
static const ThemePreferences defaults = ThemePreferences(); static const defaults = ThemePreferences();
} }
extension ThemePreferencesExt on ThemePreferences { extension ThemePreferencesExt on ThemePreferences {

View file

@ -32,7 +32,7 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
bool _passwordVisible = false; var _passwordVisible = false;
@override @override
void initState() { void initState() {
@ -47,7 +47,6 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
} }
@override @override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;

View file

@ -1,14 +1,12 @@
import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:animated_theme_switcher/animated_theme_switcher.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../settings/settings.dart'; import '../../../settings/settings.dart';
import '../models/models.dart'; import '../../models/models.dart';
import '../views.dart';
const String formFieldBrightness = 'brightness'; List<DropdownMenuItem<BrightnessPreference>> _getBrightnessDropdownItems() {
List<DropdownMenuItem<dynamic>> _getBrightnessDropdownItems() {
const brightnessPrefs = BrightnessPreference.values; const brightnessPrefs = BrightnessPreference.values;
final brightnessNames = { final brightnessNames = {
BrightnessPreference.system: translate('brightness.system'), BrightnessPreference.system: translate('brightness.system'),
@ -22,25 +20,21 @@ List<DropdownMenuItem<dynamic>> _getBrightnessDropdownItems() {
} }
Widget buildSettingsPageBrightnessPreferences( Widget buildSettingsPageBrightnessPreferences(
{required BuildContext context, {required BuildContext context, required ThemeSwitcherState switcher}) {
required void Function() onChanged,
required ThemeSwitcherState switcher}) {
final preferencesRepository = PreferencesRepository.instance; final preferencesRepository = PreferencesRepository.instance;
final themePreferences = preferencesRepository.value.themePreference; final themePreferences = preferencesRepository.value.themePreference;
return FormBuilderDropdown(
name: formFieldBrightness, return StyledDropdown<BrightnessPreference>(
decoration: InputDecoration(
label: Text(translate('settings_page.brightness_mode'))),
items: _getBrightnessDropdownItems(), items: _getBrightnessDropdownItems(),
initialValue: themePreferences.brightnessPreference, value: themePreferences.brightnessPreference,
decoratorLabel: translate('settings_page.brightness_mode'),
onChanged: (value) async { onChanged: (value) async {
final newThemePrefs = themePreferences.copyWith( final newThemePrefs =
brightnessPreference: value as BrightnessPreference); themePreferences.copyWith(brightnessPreference: value);
final newPrefs = preferencesRepository.value final newPrefs = preferencesRepository.value
.copyWith(themePreference: newThemePrefs); .copyWith(themePreference: newThemePrefs);
await preferencesRepository.set(newPrefs); await preferencesRepository.set(newPrefs);
switcher.changeTheme(theme: newThemePrefs.themeData()); switcher.changeTheme(theme: newThemePrefs.themeData());
onChanged();
}); });
} }

View file

@ -1,16 +1,12 @@
import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:animated_theme_switcher/animated_theme_switcher.dart';
import 'package:async_tools/async_tools.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../settings/settings.dart'; import '../../../settings/settings.dart';
import '../models/models.dart'; import '../../models/models.dart';
import '../views.dart';
const String formFieldTheme = 'theme'; List<DropdownMenuItem<ColorPreference>> _getThemeDropdownItems() {
const String _kSwitchTheme = 'switchTheme';
List<DropdownMenuItem<dynamic>> _getThemeDropdownItems() {
const colorPrefs = ColorPreference.values; const colorPrefs = ColorPreference.values;
final colorNames = { final colorNames = {
ColorPreference.scarlet: translate('themes.scarlet'), ColorPreference.scarlet: translate('themes.scarlet'),
@ -34,27 +30,20 @@ List<DropdownMenuItem<dynamic>> _getThemeDropdownItems() {
} }
Widget buildSettingsPageColorPreferences( Widget buildSettingsPageColorPreferences(
{required BuildContext context, {required BuildContext context, required ThemeSwitcherState switcher}) {
required void Function() onChanged,
required ThemeSwitcherState switcher}) {
final preferencesRepository = PreferencesRepository.instance; final preferencesRepository = PreferencesRepository.instance;
final themePreferences = preferencesRepository.value.themePreference; final themePreferences = preferencesRepository.value.themePreference;
return FormBuilderDropdown(
name: formFieldTheme, return StyledDropdown<ColorPreference>(
decoration:
InputDecoration(label: Text(translate('settings_page.color_theme'))),
items: _getThemeDropdownItems(), items: _getThemeDropdownItems(),
initialValue: themePreferences.colorPreference, value: themePreferences.colorPreference,
onChanged: (value) { decoratorLabel: translate('settings_page.color_theme'),
singleFuture(_kSwitchTheme, () async { onChanged: (value) async {
final newThemePrefs = themePreferences.copyWith( final newThemePrefs = themePreferences.copyWith(colorPreference: value);
colorPreference: value as ColorPreference);
final newPrefs = preferencesRepository.value final newPrefs = preferencesRepository.value
.copyWith(themePreference: newThemePrefs); .copyWith(themePreference: newThemePrefs);
await preferencesRepository.set(newPrefs); await preferencesRepository.set(newPrefs);
switcher.changeTheme(theme: newThemePrefs.themeData()); switcher.changeTheme(theme: newThemePrefs.themeData());
onChanged();
});
}); });
} }

View file

@ -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 = <double>[
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<PreferencesCubit>().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<PreferencesCubit>().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]);
}
}

View file

@ -0,0 +1,4 @@
export 'brightness_preferences.dart';
export 'color_preferences.dart';
export 'display_scale_preferences.dart';
export 'wallpaper_preferences.dart';

View file

@ -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());
});
}

View file

@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
final isiOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; 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 && final isMobile = !kIsWeb &&
(defaultTargetPlatform == TargetPlatform.iOS || (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.android); defaultTargetPlatform == TargetPlatform.android);

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:rflutter_alert/rflutter_alert.dart'; import 'package:rflutter_alert/rflutter_alert.dart';
import '../theme.dart'; import '../../theme.dart';
AlertStyle _alertStyle(BuildContext context) { AlertStyle _alertStyle(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -186,6 +186,7 @@ Future<void> showAlertWidgetModal(
child: Text( child: Text(
translate('button.ok'), translate('button.ok'),
style: _buttonTextStyle(context), style: _buttonTextStyle(context),
softWrap: true,
), ),
) )
], ],

View file

@ -2,10 +2,10 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../theme.dart'; import '../../theme.dart';
class AvatarWidget extends StatelessWidget { class StyledAvatar extends StatelessWidget {
const AvatarWidget({ const StyledAvatar({
required String name, required String name,
required double size, required double size,
required Color borderColor, required Color borderColor,
@ -44,7 +44,6 @@ class AvatarWidget extends StatelessWidget {
width: 1 * (_size ~/ 32 + 1), width: 1 * (_size ~/ 32 + 1),
strokeAlign: BorderSide.strokeAlignOutside)), strokeAlign: BorderSide.strokeAlignOutside)),
child: AvatarImage( child: AvatarImage(
//size: 32,
backgroundImage: _imageProvider, backgroundImage: _imageProvider,
backgroundColor: backgroundColor:
_scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders _scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders
@ -59,7 +58,7 @@ class AvatarWidget extends StatelessWidget {
? _backgroundColor ? _backgroundColor
: _foregroundColor, : _foregroundColor,
), ),
).fit().paddingAll(_size / 16))); ).fit().paddingAll(_size / 16.scaled(context))));
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -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<void> 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<void> Function(bool)? _onChanged;
final bool _value;
}

View file

@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme.dart'; import '../../theme.dart';
class StyledDialog extends StatelessWidget { class StyledDialog extends StatelessWidget {
const StyledDialog({required this.title, required this.child, super.key}); const StyledDialog({required this.title, required this.child, super.key});

View file

@ -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<T> extends StatelessWidget {
const StyledDropdown(
{required List<DropdownMenuItem<T>> items,
required T value,
String? decoratorLabel,
Future<void> 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<ScaleScheme>()!;
Widget ctrl = DropdownButton<T>(
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<DropdownMenuItem<T>> _items;
final String? _decoratorLabel;
final Future<void> Function(T)? _onChanged;
final T _value;
}

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme.dart'; import '../../theme.dart';
class StyledScaffold extends StatelessWidget { class StyledScaffold extends StatelessWidget {
const StyledScaffold({required this.appBar, required this.body, super.key}); const StyledScaffold({required this.appBar, required this.body, super.key});

View file

@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import '../theme.dart'; import '../../theme.dart';
class SliderTileAction { class SlideTileAction {
const SliderTileAction({ const SlideTileAction({
required this.actionScale, required this.actionScale,
required this.onPressed, required this.onPressed,
this.key, this.key,
@ -20,8 +20,8 @@ class SliderTileAction {
final SlidableActionCallback? onPressed; final SlidableActionCallback? onPressed;
} }
class SliderTile extends StatelessWidget { class StyledSlideTile extends StatelessWidget {
const SliderTile( const StyledSlideTile(
{required this.disabled, {required this.disabled,
required this.selected, required this.selected,
required this.tileScale, required this.tileScale,
@ -38,8 +38,8 @@ class SliderTile extends StatelessWidget {
final bool disabled; final bool disabled;
final bool selected; final bool selected;
final ScaleKind tileScale; final ScaleKind tileScale;
final List<SliderTileAction> endActions; final List<SlideTileAction> endActions;
final List<SliderTileAction> startActions; final List<SlideTileAction> startActions;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
final GestureTapCallback? onDoubleTap; final GestureTapCallback? onDoubleTap;
final Widget? leading; final Widget? leading;
@ -54,8 +54,8 @@ class SliderTile extends StatelessWidget {
..add(DiagnosticsProperty<bool>('disabled', disabled)) ..add(DiagnosticsProperty<bool>('disabled', disabled))
..add(DiagnosticsProperty<bool>('selected', selected)) ..add(DiagnosticsProperty<bool>('selected', selected))
..add(DiagnosticsProperty<ScaleKind>('tileScale', tileScale)) ..add(DiagnosticsProperty<ScaleKind>('tileScale', tileScale))
..add(IterableProperty<SliderTileAction>('endActions', endActions)) ..add(IterableProperty<SlideTileAction>('endActions', endActions))
..add(IterableProperty<SliderTileAction>('startActions', startActions)) ..add(IterableProperty<SlideTileAction>('startActions', startActions))
..add(ObjectFlagProperty<GestureTapCallback?>.has('onTap', onTap)) ..add(ObjectFlagProperty<GestureTapCallback?>.has('onTap', onTap))
..add(DiagnosticsProperty<Widget?>('leading', leading)) ..add(DiagnosticsProperty<Widget?>('leading', leading))
..add(StringProperty('title', title)) ..add(StringProperty('title', title))

View file

@ -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<void> 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<ScaleTheme>()!;
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<void> Function(double)? _onChanged;
final double _value;
final Widget? _leftWidget;
final Widget? _rightWidget;
final double _min;
final double _max;
final int? _divisions;
}

View file

@ -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';

View file

@ -1,16 +1,10 @@
export 'avatar_widget.dart';
export 'brightness_preferences.dart';
export 'color_preferences.dart';
export 'enter_password.dart'; export 'enter_password.dart';
export 'enter_pin.dart'; export 'enter_pin.dart';
export 'option_box.dart'; export 'option_box.dart';
export 'pop_control.dart'; export 'pop_control.dart';
export 'preferences/preferences.dart';
export 'recovery_key_widget.dart'; export 'recovery_key_widget.dart';
export 'responsive.dart'; export 'responsive.dart';
export 'scanner_error_widget.dart'; export 'scanner_error_widget.dart';
export 'slider_tile.dart'; export 'styled_widgets/styled_widgets.dart';
export 'styled_alert.dart';
export 'styled_dialog.dart';
export 'styled_scaffold.dart';
export 'wallpaper_preferences.dart';
export 'widget_helpers.dart'; export 'widget_helpers.dart';

View file

@ -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();
}
});
}

View file

@ -222,6 +222,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
return Scaffold( return Scaffold(
backgroundColor: scale.primaryScale.border, backgroundColor: scale.primaryScale.border,
appBar: DefaultAppBar( appBar: DefaultAppBar(
context: context,
title: Text(translate('developer.title')), title: Text(translate('developer.title')),
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText), icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText),

View file

@ -19,7 +19,7 @@ class SignalStrengthMeterWidget extends StatelessWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
const iconSize = 16.0; final iconSize = 16.0.scaled(context);
return BlocBuilder<ConnectionStateCubit, return BlocBuilder<ConnectionStateCubit,
AsyncValue<ProcessorConnectionState>>(builder: (context, state) { AsyncValue<ProcessorConnectionState>>(builder: (context, state) {