mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-02 18:16:59 -04:00
Accessibility update
This commit is contained in:
parent
be8014c97a
commit
3b1cb53b8a
55 changed files with 1089 additions and 807 deletions
|
@ -6,6 +6,9 @@
|
|||
- Deprecated accounts no longer crash application at startup
|
||||
- Simplify SingleContactMessagesCubit and MessageReconciliation
|
||||
- Update flutter_chat_ui to 2.0.0
|
||||
- Accessibility improvements
|
||||
- Text scaling
|
||||
- Keyboard shortcuts Ctrl + / Ctrl - to change font size
|
||||
|
||||
## v0.4.7 ##
|
||||
- *Community Contributions*
|
||||
|
|
|
@ -276,6 +276,7 @@
|
|||
"titlebar": "Settings",
|
||||
"color_theme": "Color Theme",
|
||||
"brightness_mode": "Brightness Mode",
|
||||
"display_scale": "Display Scale",
|
||||
"display_beta_warning": "Display beta warning on startup",
|
||||
"none": "None",
|
||||
"in_app": "In-app",
|
||||
|
|
|
@ -6,54 +6,9 @@ PODS:
|
|||
- Flutter (1.0.0)
|
||||
- flutter_native_splash (2.4.3):
|
||||
- Flutter
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleMLKit/BarcodeScanning (7.0.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitBarcodeScanning (~> 6.0.0)
|
||||
- GoogleMLKit/MLKitCore (7.0.0):
|
||||
- MLKitCommon (~> 12.0.0)
|
||||
- GoogleToolboxForMac/Defines (4.2.1)
|
||||
- GoogleToolboxForMac/Logger (4.2.1):
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- GoogleUtilities/Environment (8.0.2):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.0.2):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.0.2)
|
||||
- GoogleUtilities/UserDefaults (8.0.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- MLImage (1.0.0-beta6)
|
||||
- MLKitBarcodeScanning (6.0.0):
|
||||
- MLKitCommon (~> 12.0)
|
||||
- MLKitVision (~> 8.0)
|
||||
- MLKitCommon (12.0.0):
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLKitVision (8.0.0):
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLImage (= 1.0.0-beta6)
|
||||
- MLKitCommon (~> 12.0)
|
||||
- mobile_scanner (6.0.2):
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- GoogleMLKit/BarcodeScanning (~> 7.0.0)
|
||||
- nanopb (3.30910.0):
|
||||
- nanopb/decode (= 3.30910.0)
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- pasteboard (0.0.1):
|
||||
|
@ -63,7 +18,6 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- printing (1.0.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
|
@ -84,7 +38,7 @@ DEPENDENCIES:
|
|||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
|
@ -96,20 +50,6 @@ DEPENDENCIES:
|
|||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- veilid (from `.symlinks/plugins/veilid/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GTMSessionFetcher
|
||||
- MLImage
|
||||
- MLKitBarcodeScanning
|
||||
- MLKitCommon
|
||||
- MLKitVision
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
camera_avfoundation:
|
||||
:path: ".symlinks/plugins/camera_avfoundation/ios"
|
||||
|
@ -120,7 +60,7 @@ EXTERNAL SOURCES:
|
|||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
pasteboard:
|
||||
|
@ -143,26 +83,15 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/veilid/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf
|
||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
||||
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
||||
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
||||
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
||||
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
||||
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
|
|
|
@ -139,7 +139,6 @@
|
|||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
02C44F9283ADDE9FAAA73512 /* [CP] Embed Pods Frameworks */,
|
||||
61BE8A90522682C17620991D /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -232,23 +231,6 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
61BE8A90522682C17620991D /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
@ -43,6 +44,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
|
|
@ -73,15 +73,18 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
title: translate('edit_account_page.remove_account_confirm'),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(translate('edit_account_page.remove_account_confirm_message'))
|
||||
.paddingLTRB(24, 24, 24, 0),
|
||||
Text(translate('confirmation.are_you_sure')).paddingAll(8),
|
||||
.paddingLTRB(24.scaled(context), 24.scaled(context),
|
||||
24.scaled(context), 0),
|
||||
Text(translate('confirmation.are_you_sure'))
|
||||
.paddingAll(8.scaled(context)),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Icon(Icons.cancel, size: 16.scaled(context))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0),
|
||||
Text(translate('button.no')).paddingLTRB(0, 0, 4, 0)
|
||||
])),
|
||||
ElevatedButton(
|
||||
|
@ -89,10 +92,12 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0)
|
||||
Icon(Icons.check, size: 16.scaled(context))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0),
|
||||
Text(translate('button.yes'))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0)
|
||||
]))
|
||||
]).paddingAll(24)
|
||||
]).paddingAll(24.scaled(context))
|
||||
]));
|
||||
if (confirmed != null && confirmed) {
|
||||
try {
|
||||
|
@ -141,29 +146,36 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
title: translate('edit_account_page.destroy_account_confirm'),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(translate('edit_account_page.destroy_account_confirm_message'))
|
||||
.paddingLTRB(24, 24, 24, 0),
|
||||
.paddingLTRB(24.scaled(context), 24.scaled(context),
|
||||
24.scaled(context), 0),
|
||||
Text(translate(
|
||||
'edit_account_page.destroy_account_confirm_message_details'))
|
||||
.paddingLTRB(24, 24, 24, 0),
|
||||
Text(translate('confirmation.are_you_sure')).paddingAll(8),
|
||||
.paddingLTRB(24.scaled(context), 24.scaled(context),
|
||||
24.scaled(context), 0),
|
||||
Text(translate('confirmation.are_you_sure'))
|
||||
.paddingAll(24.scaled(context)),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text(translate('button.no')).paddingLTRB(0, 0, 4, 0)
|
||||
Icon(Icons.cancel, size: 16.scaled(context))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0),
|
||||
Text(translate('button.no'))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0)
|
||||
])),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0)
|
||||
Icon(Icons.check, size: 16.scaled(context))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0),
|
||||
Text(translate('button.yes'))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0)
|
||||
]))
|
||||
]).paddingAll(24)
|
||||
]).paddingAll(24.scaled(context))
|
||||
]));
|
||||
if (confirmed != null && confirmed) {
|
||||
try {
|
||||
|
@ -250,10 +262,12 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
|
||||
return StyledScaffold(
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(translate('edit_account_page.titlebar')),
|
||||
leading: Navigator.canPop(context)
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
iconSize: 24.scaled(context),
|
||||
onPressed: () {
|
||||
singleFuture((this, _kDoBackArrow), () async {
|
||||
if (_isModified) {
|
||||
|
@ -277,6 +291,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
const SignalStrengthMeterWidget(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
iconSize: 24.scaled(context),
|
||||
tooltip: translate('menu.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
await GoRouterHelper(context).push('/settings');
|
||||
|
@ -285,14 +300,14 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
body: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
_editAccountForm(context).paddingLTRB(0, 0, 0, 32),
|
||||
OptionBox(
|
||||
StyledButtonBox(
|
||||
instructions:
|
||||
translate('edit_account_page.remove_account_description'),
|
||||
buttonIcon: Icons.person_remove_alt_1,
|
||||
buttonText: translate('edit_account_page.remove_account'),
|
||||
onClick: _onRemoveAccount,
|
||||
),
|
||||
OptionBox(
|
||||
StyledButtonBox(
|
||||
instructions:
|
||||
translate('edit_account_page.destroy_account_description'),
|
||||
buttonIcon: Icons.person_off,
|
||||
|
|
|
@ -53,16 +53,16 @@ class EditProfileForm extends StatefulWidget {
|
|||
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue));
|
||||
}
|
||||
|
||||
static const String formFieldName = 'name';
|
||||
static const String formFieldPronouns = 'pronouns';
|
||||
static const String formFieldAbout = 'about';
|
||||
static const String formFieldAvailability = 'availability';
|
||||
static const String formFieldFreeMessage = 'free_message';
|
||||
static const String formFieldAwayMessage = 'away_message';
|
||||
static const String formFieldBusyMessage = 'busy_message';
|
||||
static const String formFieldAvatar = 'avatar';
|
||||
static const String formFieldAutoAway = 'auto_away';
|
||||
static const String formFieldAutoAwayTimeout = 'auto_away_timeout';
|
||||
static const formFieldName = 'name';
|
||||
static const formFieldPronouns = 'pronouns';
|
||||
static const formFieldAbout = 'about';
|
||||
static const formFieldAvailability = 'availability';
|
||||
static const formFieldFreeMessage = 'free_message';
|
||||
static const formFieldAwayMessage = 'away_message';
|
||||
static const formFieldBusyMessage = 'busy_message';
|
||||
static const formFieldAvatar = 'avatar';
|
||||
static const formFieldAutoAway = 'auto_away';
|
||||
static const formFieldAutoAwayTimeout = 'auto_away_timeout';
|
||||
}
|
||||
|
||||
class _EditProfileFormState extends State<EditProfileForm> {
|
||||
|
@ -98,6 +98,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
name: EditProfileForm.formFieldAvailability,
|
||||
initialValue: initialValue,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_availability'),
|
||||
hintText: translate('account.empty_busy_message')),
|
||||
|
@ -110,7 +111,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
Text(x == proto.Availability.AVAILABILITY_OFFLINE
|
||||
? translate('availability.always_show_offline')
|
||||
: AvailabilityWidget.availabilityName(x))
|
||||
.paddingLTRB(8, 0, 0, 0),
|
||||
.paddingLTRB(8.scaled(context), 0, 0, 0),
|
||||
])))
|
||||
.toList(),
|
||||
);
|
||||
|
@ -175,17 +176,8 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
late final Color border;
|
||||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
||||
border = scale.primaryScale.elementBackground;
|
||||
} else {
|
||||
border = scale.primaryScale.border;
|
||||
}
|
||||
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
|
@ -197,14 +189,9 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
children: [
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
AvatarWidget(
|
||||
StyledAvatar(
|
||||
name: _currentValueName,
|
||||
size: 128,
|
||||
borderColor: border,
|
||||
foregroundColor: scale.primaryScale.primaryText,
|
||||
backgroundColor: scale.primaryScale.primary,
|
||||
scaleConfig: scaleConfig,
|
||||
textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64),
|
||||
size: 128.scaled(context),
|
||||
).paddingLTRB(0, 0, 0, 16),
|
||||
const Spacer()
|
||||
]),
|
||||
|
@ -218,6 +205,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_name'),
|
||||
hintText: translate('account.empty_name')),
|
||||
|
@ -233,6 +221,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
initialValue: _savedValue.pronouns,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_pronouns'),
|
||||
hintText: translate('account.empty_pronouns')),
|
||||
|
@ -245,6 +234,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
maxLines: 8,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_about'),
|
||||
hintText: translate('account.empty_about')),
|
||||
|
@ -256,6 +246,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
initialValue: _savedValue.freeMessage,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_free_message'),
|
||||
hintText: translate('account.empty_free_message')),
|
||||
|
@ -266,6 +257,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
initialValue: _savedValue.awayMessage,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_away_message'),
|
||||
hintText: translate('account.empty_away_message')),
|
||||
|
@ -276,6 +268,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
initialValue: _savedValue.busyMessage,
|
||||
maxLength: 128,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: translate('account.form_busy_message'),
|
||||
hintText: translate('account.empty_busy_message')),
|
||||
|
@ -291,12 +284,13 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
_currentValueAutoAway = v ?? false;
|
||||
});
|
||||
},
|
||||
).paddingLTRB(0, 0, 0, 16),
|
||||
).paddingLTRB(0, 0, 0, 16.scaled(context)),
|
||||
FormBuilderTextField(
|
||||
name: EditProfileForm.formFieldAutoAwayTimeout,
|
||||
enabled: _currentValueAutoAway,
|
||||
initialValue: _savedValue.autoAwayTimeout.toString(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(8).scaled(context),
|
||||
labelText: translate('account.form_auto_away_timeout'),
|
||||
),
|
||||
validator: FormBuilderValidators.positiveNumber(),
|
||||
|
@ -306,7 +300,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
const Spacer(),
|
||||
Text(widget.instructions).toCenter().flexible(flex: 6),
|
||||
const Spacer(),
|
||||
]).paddingSymmetric(vertical: 16),
|
||||
]).paddingSymmetric(vertical: 16.scaled(context)),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Builder(builder: (context) {
|
||||
|
@ -319,17 +313,19 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
false;
|
||||
|
||||
return ElevatedButton(
|
||||
onPressed: (networkReady && _isModified) ? _doSubmit : null,
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(networkReady ? Icons.check : Icons.hourglass_empty,
|
||||
size: 16)
|
||||
.paddingLTRB(0, 0, 4, 0),
|
||||
Text(networkReady
|
||||
? widget.submitText
|
||||
: widget.submitDisabledText)
|
||||
.paddingLTRB(0, 0, 4, 0)
|
||||
]),
|
||||
);
|
||||
onPressed: (networkReady && _isModified) ? _doSubmit : null,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsGeometry.all(4.scaled(context)),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(networkReady ? Icons.check : Icons.hourglass_empty,
|
||||
size: 16.scaled(context))
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0),
|
||||
Text(networkReady
|
||||
? widget.submitText
|
||||
: widget.submitDisabledText)
|
||||
.paddingLTRB(0, 0, 4.scaled(context), 0)
|
||||
]),
|
||||
));
|
||||
}),
|
||||
const Spacer()
|
||||
])
|
||||
|
@ -363,5 +359,5 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||
late AccountSpec _savedValue;
|
||||
late bool _currentValueAutoAway;
|
||||
late String _currentValueName;
|
||||
bool _isModified = false;
|
||||
var _isModified = false;
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||
|
||||
return StyledScaffold(
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(translate('new_account_page.titlebar')),
|
||||
leading: GoRouterHelper(context).canPop()
|
||||
? IconButton(
|
||||
|
@ -111,6 +112,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||
const SignalStrengthMeterWidget(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
iconSize: 24.scaled(context),
|
||||
tooltip: translate('menu.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
await GoRouterHelper(context).push('/settings');
|
||||
|
|
|
@ -164,6 +164,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
|
|||
|
||||
return StyledScaffold(
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(translate('show_recovery_key_page.titlebar')),
|
||||
actions: [
|
||||
const SignalStrengthMeterWidget(),
|
||||
|
@ -193,7 +194,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
|
|||
textAlign: TextAlign.center,
|
||||
translate('show_recovery_key_page.instructions_options'))
|
||||
.paddingLTRB(12, 0, 12, 24),
|
||||
OptionBox(
|
||||
StyledButtonBox(
|
||||
instructions:
|
||||
translate('show_recovery_key_page.instructions_print'),
|
||||
buttonIcon: Icons.print,
|
||||
|
@ -209,7 +210,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
|
|||
_codeHandled = true;
|
||||
});
|
||||
}),
|
||||
OptionBox(
|
||||
StyledButtonBox(
|
||||
instructions:
|
||||
translate('show_recovery_key_page.instructions_view'),
|
||||
buttonIcon: Icons.edit_document,
|
||||
|
@ -229,7 +230,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
|
|||
_codeHandled = true;
|
||||
});
|
||||
}),
|
||||
OptionBox(
|
||||
StyledButtonBox(
|
||||
instructions:
|
||||
translate('show_recovery_key_page.instructions_share'),
|
||||
buttonIcon: Icons.ios_share,
|
||||
|
|
|
@ -31,7 +31,7 @@ class VeilidChatApp extends StatelessWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
static const String name = 'VeilidChat';
|
||||
static const name = 'VeilidChat';
|
||||
|
||||
final ThemeData initialThemeData;
|
||||
|
||||
|
@ -125,7 +125,7 @@ class VeilidChatApp extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) => FutureProvider<VeilidChatGlobalInit?>(
|
||||
initialData: null,
|
||||
create: (context) async => VeilidChatGlobalInit.initialize(),
|
||||
create: (context) => VeilidChatGlobalInit.initialize(),
|
||||
builder: (context, __) {
|
||||
final globalInit = context.watch<VeilidChatGlobalInit?>();
|
||||
if (globalInit == null) {
|
||||
|
|
|
@ -355,6 +355,7 @@ class _VcComposerState extends State<VcComposerWidget> {
|
|||
borderRadius: BorderRadius.all(Radius.circular(
|
||||
8 * config.borderRadiusScale))),
|
||||
hintText: widget.hintText,
|
||||
hintMaxLines: 1,
|
||||
hintStyle: chatTheme.typography.bodyMedium.copyWith(
|
||||
color: widget.hintColor ??
|
||||
chatTheme.colors.onSurface
|
||||
|
|
|
@ -12,7 +12,7 @@ class VcTextMessageWidget extends StatelessWidget {
|
|||
const VcTextMessageWidget({
|
||||
required this.message,
|
||||
required this.index,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
this.padding,
|
||||
this.borderRadius,
|
||||
this.onlyEmojiFontSize,
|
||||
this.sentBackgroundColor,
|
||||
|
@ -72,10 +72,6 @@ class VcTextMessageWidget extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scaleTheme = theme.extension<ScaleTheme>()!;
|
||||
final config = scaleTheme.config;
|
||||
final scheme = scaleTheme.scheme;
|
||||
final scale = scaleTheme.scheme.scale(ScaleKind.primary);
|
||||
final textTheme = theme.textTheme;
|
||||
final scaleChatTheme = scaleTheme.chatTheme();
|
||||
final chatTheme = scaleChatTheme.chatTheme;
|
||||
|
||||
|
@ -243,15 +239,16 @@ class TimeAndStatus extends StatelessWidget {
|
|||
if (showStatus && status != null)
|
||||
if (status == MessageStatus.sending)
|
||||
SizedBox(
|
||||
width: 6,
|
||||
height: 6,
|
||||
width: 6.scaled(context),
|
||||
height: 6.scaled(context),
|
||||
child: CircularProgressIndicator(
|
||||
color: textStyle?.color,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(getIconForStatus(status!), color: textStyle?.color, size: 12),
|
||||
Icon(getIconForStatus(status!),
|
||||
color: textStyle?.color, size: 12.scaled(context)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -132,17 +132,9 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
final scale = scaleTheme.scheme.scale(ScaleKind.primary);
|
||||
final textTheme = theme.textTheme;
|
||||
final scaleChatTheme = scaleTheme.chatTheme();
|
||||
// final errorChatTheme = chatTheme.copyWith(color:)
|
||||
// ..inputTextColor = scaleScheme.errorScale.primary
|
||||
// ..sendButtonIcon = Image.asset(
|
||||
// 'assets/icon-send.png',
|
||||
// color: scaleScheme.errorScale.primary,
|
||||
// package: 'flutter_chat_ui',
|
||||
// ))
|
||||
// .commit();
|
||||
|
||||
// Get the enclosing chat component cubit that contains our state
|
||||
// (created by ChatComponentWidget.builder())
|
||||
// (created by ChatComponentWidget.singleContact())
|
||||
final chatComponentCubit = context.watch<ChatComponentCubit>();
|
||||
final chatComponentState = chatComponentCubit.state;
|
||||
|
||||
|
@ -273,14 +265,19 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
// Text message builder
|
||||
textMessageBuilder: (context, message, index) =>
|
||||
VcTextMessageWidget(
|
||||
message: message,
|
||||
index: index,
|
||||
// showTime: true,
|
||||
// showStatus: true,
|
||||
),
|
||||
message: message,
|
||||
index: index,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 16)
|
||||
.scaled(context)
|
||||
// showTime: true,
|
||||
// showStatus: true,
|
||||
),
|
||||
// Composer builder
|
||||
composerBuilder: (ctx) => VcComposerWidget(
|
||||
autofocus: true,
|
||||
padding: const EdgeInsets.all(4).scaled(context),
|
||||
gap: 8.scaled(context),
|
||||
focusNode: _focusNode,
|
||||
textInputAction: isAnyMobile
|
||||
? TextInputAction.newline
|
||||
|
|
|
@ -24,11 +24,9 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||
final bool _disabled;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final scaleTheme = Theme.of(context).extension<ScaleTheme>()!;
|
||||
|
||||
final activeChatCubit = context.watch<ActiveChatCubit>();
|
||||
|
@ -48,23 +46,12 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||
selected: selected,
|
||||
);
|
||||
|
||||
final avatar = AvatarWidget(
|
||||
final avatar = StyledAvatar(
|
||||
name: name,
|
||||
size: 32,
|
||||
borderColor: scaleTheme.config.useVisualIndicators
|
||||
? scaleTheme.scheme.primaryScale.primaryText
|
||||
: scaleTheme.scheme.primaryScale.subtleBorder,
|
||||
foregroundColor: _disabled
|
||||
? scaleTheme.scheme.grayScale.primaryText
|
||||
: scaleTheme.scheme.primaryScale.primaryText,
|
||||
backgroundColor: _disabled
|
||||
? scaleTheme.scheme.grayScale.primary
|
||||
: scaleTheme.scheme.primaryScale.primary,
|
||||
scaleConfig: scaleTheme.config,
|
||||
textStyle: theme.textTheme.titleLarge!,
|
||||
size: 32.scaled(context),
|
||||
);
|
||||
|
||||
return SliderTile(
|
||||
return StyledSlideTile(
|
||||
key: ValueKey(_localConversationRecordKey),
|
||||
disabled: _disabled,
|
||||
selected: selected,
|
||||
|
@ -75,14 +62,14 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||
trailing: AvailabilityWidget(
|
||||
availability: availability,
|
||||
color: scaleTileTheme.textColor,
|
||||
).fit(fit: BoxFit.scaleDown),
|
||||
).fit(fit: BoxFit.fill),
|
||||
onTap: () {
|
||||
singleFuture(activeChatCubit, () async {
|
||||
activeChatCubit.setActiveChat(_localConversationRecordKey);
|
||||
});
|
||||
},
|
||||
endActions: [
|
||||
SliderTileAction(
|
||||
SlideTileAction(
|
||||
//icon: Icons.delete,
|
||||
label: translate('button.delete'),
|
||||
actionScale: ScaleKind.tertiary,
|
||||
|
|
|
@ -44,7 +44,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
|||
title = contactInvitationRecord.message;
|
||||
}
|
||||
|
||||
return SliderTile(
|
||||
return StyledSlideTile(
|
||||
key: ObjectKey(contactInvitationRecord),
|
||||
disabled: tileDisabled,
|
||||
selected: selected,
|
||||
|
@ -67,7 +67,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
|||
)));
|
||||
},
|
||||
endActions: [
|
||||
SliderTileAction(
|
||||
SlideTileAction(
|
||||
// icon: Icons.delete,
|
||||
label: translate('button.delete'),
|
||||
actionScale: ScaleKind.tertiary,
|
||||
|
|
|
@ -1,37 +1,34 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
|
||||
class AvailabilityWidget extends StatelessWidget {
|
||||
const AvailabilityWidget(
|
||||
{required this.availability,
|
||||
required this.color,
|
||||
this.vertical = true,
|
||||
this.size = 32,
|
||||
super.key});
|
||||
|
||||
static Widget availabilityIcon(proto.Availability availability, Color color,
|
||||
{double size = 24}) {
|
||||
static Widget availabilityIcon(
|
||||
proto.Availability availability,
|
||||
Color color,
|
||||
) {
|
||||
late final Widget icon;
|
||||
switch (availability) {
|
||||
case proto.Availability.AVAILABILITY_AWAY:
|
||||
icon = SvgPicture.asset('assets/images/toilet.svg',
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter: ColorFilter.mode(color, BlendMode.srcATop));
|
||||
case proto.Availability.AVAILABILITY_BUSY:
|
||||
icon = Icon(Icons.event_busy, size: size);
|
||||
icon = const Icon(Icons.event_busy, applyTextScaling: true);
|
||||
case proto.Availability.AVAILABILITY_FREE:
|
||||
icon = Icon(Icons.event_available, size: size);
|
||||
icon = const Icon(Icons.event_available, applyTextScaling: true);
|
||||
case proto.Availability.AVAILABILITY_OFFLINE:
|
||||
icon = Icon(Icons.cloud_off, size: size);
|
||||
icon = const Icon(Icons.cloud_off, applyTextScaling: true);
|
||||
case proto.Availability.AVAILABILITY_UNSPECIFIED:
|
||||
icon = Icon(Icons.question_mark, size: size);
|
||||
icon = const Icon(Icons.question_mark, applyTextScaling: true);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
@ -59,23 +56,17 @@ class AvailabilityWidget extends StatelessWidget {
|
|||
final textTheme = theme.textTheme;
|
||||
|
||||
final name = availabilityName(availability);
|
||||
final icon = availabilityIcon(availability, color, size: size * 2 / 3);
|
||||
final icon = availabilityIcon(availability, color);
|
||||
|
||||
return vertical
|
||||
? ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: size),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
icon,
|
||||
Text(name, style: textTheme.labelSmall!.copyWith(color: color))
|
||||
.fit(fit: BoxFit.scaleDown)
|
||||
]))
|
||||
: ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(height: size),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
icon,
|
||||
Text(name, style: textTheme.labelLarge!.copyWith(color: color))
|
||||
.paddingLTRB(size / 4, 0, 0, 0)
|
||||
]));
|
||||
? Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
icon,
|
||||
Text(name, style: textTheme.labelSmall!.copyWith(color: color))
|
||||
])
|
||||
: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
icon,
|
||||
Text(' $name', style: textTheme.labelLarge!.copyWith(color: color))
|
||||
]);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -83,7 +74,6 @@ class AvailabilityWidget extends StatelessWidget {
|
|||
final proto.Availability availability;
|
||||
final Color color;
|
||||
final bool vertical;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
|
@ -92,7 +82,6 @@ class AvailabilityWidget extends StatelessWidget {
|
|||
..add(
|
||||
DiagnosticsProperty<proto.Availability>('availability', availability))
|
||||
..add(DiagnosticsProperty<bool>('vertical', vertical))
|
||||
..add(DoubleProperty('size', size))
|
||||
..add(ColorProperty('color', color));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,30 +26,16 @@ class ContactItemWidget extends StatelessWidget {
|
|||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
|
||||
final name = _contact.nameOrNickname;
|
||||
final title = _contact.displayName;
|
||||
final subtitle = _contact.profile.status;
|
||||
|
||||
final avatar = AvatarWidget(
|
||||
final avatar = StyledAvatar(
|
||||
name: name,
|
||||
size: 34,
|
||||
borderColor: _disabled
|
||||
? scale.grayScale.primaryText
|
||||
: scale.primaryScale.subtleBorder,
|
||||
foregroundColor: _disabled
|
||||
? scale.grayScale.primaryText
|
||||
: scale.primaryScale.primaryText,
|
||||
backgroundColor:
|
||||
_disabled ? scale.grayScale.primary : scale.primaryScale.primary,
|
||||
scaleConfig: scaleConfig,
|
||||
textStyle: theme.textTheme.titleLarge!,
|
||||
size: 34.scaled(context),
|
||||
);
|
||||
|
||||
return SliderTile(
|
||||
return StyledSlideTile(
|
||||
key: ObjectKey(_contact),
|
||||
disabled: _disabled,
|
||||
selected: _selected,
|
||||
|
@ -69,7 +55,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||
}),
|
||||
startActions: [
|
||||
if (_onDoubleTap != null)
|
||||
SliderTileAction(
|
||||
SlideTileAction(
|
||||
//icon: Icons.edit,
|
||||
label: translate('button.chat'),
|
||||
actionScale: ScaleKind.secondary,
|
||||
|
@ -81,7 +67,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||
],
|
||||
endActions: [
|
||||
if (_onTap != null)
|
||||
SliderTileAction(
|
||||
SlideTileAction(
|
||||
//icon: Icons.edit,
|
||||
label: translate('button.edit'),
|
||||
actionScale: ScaleKind.secondary,
|
||||
|
@ -91,7 +77,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||
}),
|
||||
),
|
||||
if (_onDelete != null)
|
||||
SliderTileAction(
|
||||
SlideTileAction(
|
||||
//icon: Icons.delete,
|
||||
label: translate('button.delete'),
|
||||
actionScale: ScaleKind.tertiary,
|
||||
|
|
|
@ -92,7 +92,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
|
||||
final menuParams = StarMenuParameters(
|
||||
shape: MenuShape.linear,
|
||||
centerOffset: const Offset(0, 64),
|
||||
centerOffset: Offset(0, 64.scaled(context)),
|
||||
boundaryBackground: BoundaryBackground(
|
||||
color: menuBackgroundColor,
|
||||
decoration: ShapeDecoration(
|
||||
|
@ -113,13 +113,14 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
onPressed: onPressed,
|
||||
icon: Icon(
|
||||
iconData,
|
||||
size: 32,
|
||||
).paddingSTEB(0, 8, 0, 8),
|
||||
size: 32.scaled(context),
|
||||
).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context)),
|
||||
label: Text(
|
||||
text,
|
||||
textScaler: MediaQuery.of(context).textScaler,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
).paddingSTEB(0, 8, 0, 8));
|
||||
).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context)));
|
||||
|
||||
final inviteMenuItems = [
|
||||
makeMenuButton(
|
||||
|
@ -135,14 +136,14 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
onPressed: () async {
|
||||
_invitationMenuController.closeMenu!();
|
||||
await ScanInvitationDialog.show(context);
|
||||
}).paddingLTRB(0, 0, 0, 8),
|
||||
}).paddingLTRB(0, 0, 0, 8.scaled(context)),
|
||||
makeMenuButton(
|
||||
iconData: Icons.contact_page,
|
||||
text: translate('add_contact_sheet.create_invite'),
|
||||
onPressed: () async {
|
||||
_invitationMenuController.closeMenu!();
|
||||
await CreateInvitationDialog.show(context);
|
||||
}).paddingLTRB(0, 0, 0, 8),
|
||||
}).paddingLTRB(0, 0, 0, 8.scaled(context)),
|
||||
];
|
||||
|
||||
return StarMenu(
|
||||
|
@ -154,7 +155,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
params: menuParams,
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
iconSize: 24,
|
||||
iconSize: 24.scaled(context),
|
||||
icon: Icon(Icons.person_add, color: menuIconColor),
|
||||
tooltip: translate('add_contact_sheet.add_contact')),
|
||||
);
|
||||
|
@ -202,13 +203,13 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
onDoubleTap: _onStartChat,
|
||||
onTap: onContactSelected,
|
||||
onDelete: _onContactDeleted)
|
||||
.paddingLTRB(0, 4, 0, 0);
|
||||
.paddingLTRB(0, 4.scaled(context), 0, 0);
|
||||
case ContactsBrowserElementKind.invitation:
|
||||
final invitation = element.invitation!;
|
||||
return ContactInvitationItemWidget(
|
||||
contactInvitationRecord: invitation,
|
||||
disabled: false)
|
||||
.paddingLTRB(0, 4, 0, 0);
|
||||
.paddingLTRB(0, 4.scaled(context), 0, 0);
|
||||
}
|
||||
},
|
||||
filter: (value) {
|
||||
|
@ -242,9 +243,11 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
}
|
||||
return filtered;
|
||||
},
|
||||
searchFieldHeight: 40,
|
||||
listViewPadding: const EdgeInsets.fromLTRB(4, 0, 4, 4),
|
||||
searchFieldPadding: const EdgeInsets.fromLTRB(4, 8, 4, 4),
|
||||
searchFieldHeight: 40.scaled(context),
|
||||
listViewPadding:
|
||||
const EdgeInsets.fromLTRB(4, 0, 4, 4).scaled(context),
|
||||
searchFieldPadding:
|
||||
const EdgeInsets.fromLTRB(4, 8, 4, 4).scaled(context),
|
||||
emptyWidget: contactList == null
|
||||
? waitingPage(
|
||||
text: translate('contact_list.loading_contacts'))
|
||||
|
@ -254,8 +257,8 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
searchFieldEnabled: contactList != null,
|
||||
inputDecoration:
|
||||
InputDecoration(labelText: translate('contact_list.search')),
|
||||
secondaryWidget:
|
||||
buildInvitationButton(context).paddingLTRB(4, 0, 0, 0))
|
||||
secondaryWidget: buildInvitationButton(context)
|
||||
.paddingLTRB(4.scaled(context), 0, 0, 0))
|
||||
.expanded()
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ class _ContactsPageState extends State<ContactsPage> {
|
|||
|
||||
return StyledScaffold(
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(
|
||||
!enableSplit && enableRight
|
||||
? translate('contacts_dialog.edit_contact')
|
||||
|
@ -47,6 +48,7 @@ class _ContactsPageState extends State<ContactsPage> {
|
|||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
iconSize: 24.scaled(context),
|
||||
onPressed: () {
|
||||
singleFuture((this, _kDoBackArrow), () async {
|
||||
final confirmed = await _onContactSelected(null);
|
||||
|
@ -65,21 +67,21 @@ class _ContactsPageState extends State<ContactsPage> {
|
|||
if (_selectedContact != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chat_bubble),
|
||||
iconSize: 24,
|
||||
iconSize: 24.scaled(context),
|
||||
color: appBarTheme.iconColor,
|
||||
tooltip: translate('contacts_dialog.new_chat'),
|
||||
onPressed: () async {
|
||||
await _onChatStarted(_selectedContact!);
|
||||
}).paddingLTRB(8, 0, 8, 0),
|
||||
}),
|
||||
if (enableSplit && _selectedContact != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
iconSize: 24,
|
||||
iconSize: 24.scaled(context),
|
||||
color: appBarTheme.iconColor,
|
||||
tooltip: translate('contacts_dialog.close_contact'),
|
||||
onPressed: () async {
|
||||
await _onContactSelected(null);
|
||||
}).paddingLTRB(8, 0, 8, 0),
|
||||
}),
|
||||
]),
|
||||
body: LayoutBuilder(builder: (context, constraint) {
|
||||
final maxWidth = constraint.maxWidth;
|
||||
|
|
|
@ -92,16 +92,8 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
Widget _editContactForm(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
late final Color border;
|
||||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
||||
border = scale.primaryScale.elementBackground;
|
||||
} else {
|
||||
border = scale.primaryScale.subtleBorder;
|
||||
}
|
||||
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
|
@ -116,18 +108,12 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
children: [
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
AvatarWidget(
|
||||
name: _currentValueNickname.isNotEmpty
|
||||
? _currentValueNickname
|
||||
: widget.contact.profile.name,
|
||||
size: 128,
|
||||
borderColor: border,
|
||||
foregroundColor: scale.primaryScale.primaryText,
|
||||
backgroundColor: scale.primaryScale.primary,
|
||||
scaleConfig: scaleConfig,
|
||||
textStyle: theme.textTheme.titleLarge!
|
||||
.copyWith(fontSize: 64),
|
||||
).paddingLTRB(0, 0, 0, 16),
|
||||
StyledAvatar(
|
||||
name: _currentValueNickname.isNotEmpty
|
||||
? _currentValueNickname
|
||||
: widget.contact.profile.name,
|
||||
size: 128)
|
||||
.paddingLTRB(0, 0, 0, 16),
|
||||
const Spacer()
|
||||
]),
|
||||
SelectableText(widget.contact.profile.name,
|
||||
|
@ -211,10 +197,11 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
ElevatedButton(
|
||||
onPressed: _isModified ? _doSubmit : null,
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text(widget.submitText).paddingLTRB(0, 0, 4, 0)
|
||||
]),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenter(),
|
||||
Icon(Icons.check, size: 24.scaled(context))
|
||||
.paddingLTRB(0, 0, 4, 0),
|
||||
Text(widget.submitText).paddingLTRB(0, 0, 4.scaled(context), 0)
|
||||
]).paddingAll(4.scaled(context)),
|
||||
).paddingSymmetric(vertical: 4.scaled(context)).alignAtCenter(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -32,6 +32,14 @@ class DeveloperPageIntent extends Intent {
|
|||
const DeveloperPageIntent();
|
||||
}
|
||||
|
||||
class DisplayScaleUpIntent extends Intent {
|
||||
const DisplayScaleUpIntent();
|
||||
}
|
||||
|
||||
class DisplayScaleDownIntent extends Intent {
|
||||
const DisplayScaleDownIntent();
|
||||
}
|
||||
|
||||
class KeyboardShortcuts extends StatelessWidget {
|
||||
const KeyboardShortcuts({required this.child, super.key});
|
||||
|
||||
|
@ -57,7 +65,7 @@ class KeyboardShortcuts extends StatelessWidget {
|
|||
});
|
||||
}
|
||||
|
||||
void changeBrightness(BuildContext context) {
|
||||
void _changeBrightness(BuildContext context) {
|
||||
singleFuture(this, () async {
|
||||
final prefs = PreferencesRepository.instance.value;
|
||||
|
||||
|
@ -79,7 +87,7 @@ class KeyboardShortcuts extends StatelessWidget {
|
|||
});
|
||||
}
|
||||
|
||||
void changeColor(BuildContext context) {
|
||||
void _changeColor(BuildContext context) {
|
||||
singleFuture(this, () async {
|
||||
final prefs = PreferencesRepository.instance.value;
|
||||
final oldColor = prefs.themePreference.colorPreference;
|
||||
|
@ -100,6 +108,54 @@ class KeyboardShortcuts extends StatelessWidget {
|
|||
});
|
||||
}
|
||||
|
||||
void _displayScaleUp(BuildContext context) {
|
||||
singleFuture(this, () async {
|
||||
final prefs = PreferencesRepository.instance.value;
|
||||
final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale);
|
||||
if (oldIndex == maxDisplayScaleIndex) {
|
||||
return;
|
||||
}
|
||||
final newIndex = oldIndex + 1;
|
||||
final newDisplayScaleName = indexToDisplayScaleName(newIndex);
|
||||
|
||||
log.info('Changing display scale to $newDisplayScaleName');
|
||||
|
||||
final newPrefs = prefs.copyWith(
|
||||
themePreference: prefs.themePreference
|
||||
.copyWith(displayScale: indexToDisplayScale(newIndex)));
|
||||
await PreferencesRepository.instance.set(newPrefs);
|
||||
|
||||
if (context.mounted) {
|
||||
ThemeSwitcher.of(context)
|
||||
.changeTheme(theme: newPrefs.themePreference.themeData());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _displayScaleDown(BuildContext context) {
|
||||
singleFuture(this, () async {
|
||||
final prefs = PreferencesRepository.instance.value;
|
||||
final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale);
|
||||
if (oldIndex == 0) {
|
||||
return;
|
||||
}
|
||||
final newIndex = oldIndex - 1;
|
||||
final newDisplayScaleName = indexToDisplayScaleName(newIndex);
|
||||
|
||||
log.info('Changing display scale to $newDisplayScaleName');
|
||||
|
||||
final newPrefs = prefs.copyWith(
|
||||
themePreference: prefs.themePreference
|
||||
.copyWith(displayScale: indexToDisplayScale(newIndex)));
|
||||
await PreferencesRepository.instance.set(newPrefs);
|
||||
|
||||
if (context.mounted) {
|
||||
ThemeSwitcher.of(context)
|
||||
.changeTheme(theme: newPrefs.themePreference.themeData());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _attachDetach(BuildContext context) {
|
||||
singleFuture(this, () async {
|
||||
if (ProcessorRepository.instance.processorConnectionState.isAttached) {
|
||||
|
@ -125,44 +181,88 @@ class KeyboardShortcuts extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) => ThemeSwitcher(
|
||||
builder: (context) => Shortcuts(
|
||||
shortcuts: const <ShortcutActivator, Intent>{
|
||||
SingleActivator(
|
||||
shortcuts: <ShortcutActivator, Intent>{
|
||||
////////////////////////// Reload Theme
|
||||
const SingleActivator(
|
||||
LogicalKeyboardKey.keyR,
|
||||
control: true,
|
||||
alt: true,
|
||||
): ReloadThemeIntent(),
|
||||
SingleActivator(
|
||||
): const ReloadThemeIntent(),
|
||||
////////////////////////// Switch Brightness
|
||||
const SingleActivator(
|
||||
LogicalKeyboardKey.keyB,
|
||||
control: true,
|
||||
alt: true,
|
||||
): ChangeBrightnessIntent(),
|
||||
SingleActivator(
|
||||
): const ChangeBrightnessIntent(),
|
||||
////////////////////////// Change Color
|
||||
const SingleActivator(
|
||||
LogicalKeyboardKey.keyC,
|
||||
control: true,
|
||||
alt: true,
|
||||
): ChangeColorIntent(),
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.keyA,
|
||||
control: true,
|
||||
alt: true,
|
||||
): AttachDetachIntent(),
|
||||
SingleActivator(
|
||||
): const ChangeColorIntent(),
|
||||
////////////////////////// Attach/Detach
|
||||
if (kIsDebugMode)
|
||||
const SingleActivator(
|
||||
LogicalKeyboardKey.keyA,
|
||||
control: true,
|
||||
alt: true,
|
||||
): const AttachDetachIntent(),
|
||||
////////////////////////// Show Developer Page
|
||||
const SingleActivator(
|
||||
LogicalKeyboardKey.keyD,
|
||||
control: true,
|
||||
alt: true,
|
||||
): DeveloperPageIntent(),
|
||||
): const DeveloperPageIntent(),
|
||||
////////////////////////// Display Scale Up
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.equal,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleUpIntent(),
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.equal,
|
||||
shift: true,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleUpIntent(),
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.add,
|
||||
shift: true,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleUpIntent(),
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.numpadAdd,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleUpIntent(),
|
||||
////////////////////////// Display Scale Down
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.minus,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleDownIntent(),
|
||||
SingleActivator(
|
||||
LogicalKeyboardKey.numpadSubtract,
|
||||
meta: isMac || isiOS,
|
||||
control: !(isMac || isiOS),
|
||||
): const DisplayScaleDownIntent(),
|
||||
},
|
||||
child: Actions(actions: <Type, Action<Intent>>{
|
||||
ReloadThemeIntent: CallbackAction<ReloadThemeIntent>(
|
||||
onInvoke: (intent) => reloadTheme(context)),
|
||||
ChangeBrightnessIntent: CallbackAction<ChangeBrightnessIntent>(
|
||||
onInvoke: (intent) => changeBrightness(context)),
|
||||
onInvoke: (intent) => _changeBrightness(context)),
|
||||
ChangeColorIntent: CallbackAction<ChangeColorIntent>(
|
||||
onInvoke: (intent) => changeColor(context)),
|
||||
onInvoke: (intent) => _changeColor(context)),
|
||||
AttachDetachIntent: CallbackAction<AttachDetachIntent>(
|
||||
onInvoke: (intent) => _attachDetach(context)),
|
||||
DeveloperPageIntent: CallbackAction<DeveloperPageIntent>(
|
||||
onInvoke: (intent) => _developerPage(context)),
|
||||
DisplayScaleUpIntent: CallbackAction<DisplayScaleUpIntent>(
|
||||
onInvoke: (intent) => _displayScaleUp(context)),
|
||||
DisplayScaleDownIntent: CallbackAction<DisplayScaleDownIntent>(
|
||||
onInvoke: (intent) => _displayScaleDown(context)),
|
||||
}, child: Focus(autofocus: true, child: child))));
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
|
|
|
@ -2,21 +2,27 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
|
||||
class DefaultAppBar extends AppBar {
|
||||
DefaultAppBar(
|
||||
{super.title,
|
||||
{required BuildContext context,
|
||||
super.title,
|
||||
super.flexibleSpace,
|
||||
super.key,
|
||||
Widget? leading,
|
||||
super.actions})
|
||||
: super(
|
||||
toolbarHeight: 40.scaled(context),
|
||||
leadingWidth: 40.scaled(context),
|
||||
leading: leading ??
|
||||
Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
margin: const EdgeInsets.all(4).scaled(context),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(32),
|
||||
shape: BoxShape.circle),
|
||||
child:
|
||||
SvgPicture.asset('assets/images/vlogo.svg', height: 24)
|
||||
.paddingAll(4)));
|
||||
child: SvgPicture.asset('assets/images/vlogo.svg',
|
||||
width: 24.scaled(context),
|
||||
height: 24.scaled(context))
|
||||
.paddingAll(4.scaled(context))));
|
||||
}
|
||||
|
|
|
@ -95,19 +95,15 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
activeBorder = scale.primary;
|
||||
}
|
||||
|
||||
final avatar = AvatarWidget(
|
||||
final avatar = StyledAvatar(
|
||||
name: name,
|
||||
size: 34,
|
||||
borderColor: border,
|
||||
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
|
||||
backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
|
||||
scaleConfig: scaleConfig,
|
||||
textStyle: theme.textTheme.titleLarge!,
|
||||
size: 34.scaled(context),
|
||||
);
|
||||
|
||||
return AnimatedPadding(
|
||||
padding: EdgeInsets.fromLTRB(selected ? 0 : 8, selected ? 0 : 2,
|
||||
selected ? 0 : 8, selected ? 0 : 2),
|
||||
selected ? 0 : 8, selected ? 0 : 2)
|
||||
.scaled(context),
|
||||
duration: const Duration(milliseconds: 50),
|
||||
child: MenuItemWidget(
|
||||
title: name,
|
||||
|
@ -144,7 +140,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
|
||||
? null
|
||||
: activeBorder,
|
||||
minHeight: 48,
|
||||
minHeight: 48.scaled(context),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -196,7 +192,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
color: scaleScheme.errorScale.subtleBorder,
|
||||
borderRadius: 12 * scaleConfig.borderRadiusScale),
|
||||
);
|
||||
loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8));
|
||||
loggedInAccounts
|
||||
.add(loggedInAccount.paddingLTRB(0, 0, 0, 8.scaled(context)));
|
||||
} else {
|
||||
// Account is not logged in
|
||||
final scale = theme.extension<ScaleScheme>()!.grayScale;
|
||||
|
@ -246,8 +243,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
}
|
||||
return IconButton(
|
||||
icon: icon,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: border,
|
||||
constraints: const BoxConstraints.expand(height: 48, width: 48),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
|
@ -286,7 +283,10 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
final scale = scaleScheme.scale(_scaleKind);
|
||||
|
||||
final settingsButton = _getButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
icon: const Icon(
|
||||
Icons.settings,
|
||||
applyTextScaling: true,
|
||||
),
|
||||
tooltip: translate('menu.settings_tooltip'),
|
||||
scale: scale,
|
||||
scaleConfig: scaleConfig,
|
||||
|
@ -295,7 +295,10 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
}).paddingLTRB(0, 0, 16, 0);
|
||||
|
||||
final addButton = _getButton(
|
||||
icon: const Icon(Icons.add),
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
applyTextScaling: true,
|
||||
),
|
||||
tooltip: translate('menu.add_account_tooltip'),
|
||||
scale: scale,
|
||||
scaleConfig: scaleConfig,
|
||||
|
@ -364,7 +367,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||
// : null)
|
||||
// .paddingLTRB(0, 0, 16, 0),
|
||||
GestureDetector(
|
||||
onLongPress: () async {
|
||||
onLongPress: () {
|
||||
context
|
||||
.findAncestorWidgetOfExactType<KeyboardShortcuts>()!
|
||||
.reloadTheme(context);
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../theme/views/preferences/preferences.dart';
|
||||
|
||||
class MenuItemWidget extends StatelessWidget {
|
||||
const MenuItemWidget({
|
||||
required this.title,
|
||||
|
@ -81,7 +83,7 @@ class MenuItemWidget extends StatelessWidget {
|
|||
hoverColor: footerButtonIconHoverColor,
|
||||
icon: Icon(
|
||||
footerButtonIcon,
|
||||
size: 24,
|
||||
size: 24.scaled(context),
|
||||
),
|
||||
onPressed: footerCallback),
|
||||
],
|
||||
|
|
|
@ -28,73 +28,81 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
|||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
constraints: const BoxConstraints.expand(height: 40, width: 40),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
scaleConfig.preferBorders
|
||||
? scale.primaryScale.hoverElementBackground
|
||||
: scale.primaryScale.hoverBorder),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
side: !scaleConfig.useVisualIndicators
|
||||
? BorderSide.none
|
||||
: BorderSide(
|
||||
strokeAlign: BorderSide.strokeAlignCenter,
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
width: 2),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8 * scaleConfig.borderRadiusScale))),
|
||||
)),
|
||||
tooltip: translate('menu.accounts_menu_tooltip'),
|
||||
onPressed: () async {
|
||||
final ctrl = context.read<ZoomDrawerController>();
|
||||
await ctrl.toggle?.call();
|
||||
});
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
applyTextScaling: true,
|
||||
),
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
scaleConfig.preferBorders
|
||||
? scale.primaryScale.hoverElementBackground
|
||||
: scale.primaryScale.hoverBorder),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
side: !scaleConfig.useVisualIndicators
|
||||
? BorderSide.none
|
||||
: BorderSide(
|
||||
strokeAlign: BorderSide.strokeAlignCenter,
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(
|
||||
8 * scaleConfig.borderRadiusScale))),
|
||||
)),
|
||||
tooltip: translate('menu.accounts_menu_tooltip'),
|
||||
onPressed: () async {
|
||||
final ctrl = context.read<ZoomDrawerController>();
|
||||
await ctrl.toggle?.call();
|
||||
}));
|
||||
});
|
||||
|
||||
Widget buildContactsButton() => Builder(builder: (context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.contacts),
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
constraints: const BoxConstraints.expand(height: 40, width: 40),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
scaleConfig.preferBorders
|
||||
? scale.primaryScale.hoverElementBackground
|
||||
: scale.primaryScale.hoverBorder),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
side: !scaleConfig.useVisualIndicators
|
||||
? BorderSide.none
|
||||
: BorderSide(
|
||||
strokeAlign: BorderSide.strokeAlignCenter,
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
width: 2),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8 * scaleConfig.borderRadiusScale))),
|
||||
)),
|
||||
tooltip: translate('menu.contacts_tooltip'),
|
||||
onPressed: () async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const ContactsPage(),
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.contacts,
|
||||
applyTextScaling: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
scaleConfig.preferBorders
|
||||
? scale.primaryScale.hoverElementBackground
|
||||
: scale.primaryScale.hoverBorder),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
side: !scaleConfig.useVisualIndicators
|
||||
? BorderSide.none
|
||||
: BorderSide(
|
||||
strokeAlign: BorderSide.strokeAlignCenter,
|
||||
color: scaleConfig.preferBorders
|
||||
? scale.primaryScale.border
|
||||
: scale.primaryScale.borderText,
|
||||
width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(
|
||||
8 * scaleConfig.borderRadiusScale))),
|
||||
)),
|
||||
tooltip: translate('menu.contacts_tooltip'),
|
||||
onPressed: () async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const ContactsPage(),
|
||||
),
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
Widget buildLeftPane(BuildContext context) => Builder(
|
||||
|
@ -112,14 +120,17 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
|||
? scale.primaryScale.subtleBackground
|
||||
: scale.primaryScale.subtleBorder,
|
||||
child: Column(children: <Widget>[
|
||||
Row(children: [
|
||||
buildMenuButton().paddingLTRB(0, 0, 8, 0),
|
||||
ProfileWidget(
|
||||
profile: profile,
|
||||
showPronouns: false,
|
||||
).expanded(),
|
||||
buildContactsButton().paddingLTRB(8, 0, 0, 0),
|
||||
]).paddingAll(8),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
buildMenuButton().paddingLTRB(0, 0, 8, 0),
|
||||
ProfileWidget(
|
||||
profile: profile,
|
||||
showPronouns: false,
|
||||
).expanded(),
|
||||
buildContactsButton().paddingLTRB(8, 0, 0, 0),
|
||||
])).paddingAll(8),
|
||||
const ChatListWidget().expanded()
|
||||
]));
|
||||
})));
|
||||
|
|
|
@ -71,8 +71,9 @@ class HomeScreenState extends State<HomeScreen>
|
|||
context: context,
|
||||
title: translate('splash.beta_title'),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
const Icon(Icons.warning, size: 64),
|
||||
Icon(Icons.warning, size: 64.scaled(context)),
|
||||
RichText(
|
||||
textScaler: MediaQuery.of(context).textScaler,
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: <TextSpan>[
|
||||
|
@ -206,34 +207,36 @@ class HomeScreenState extends State<HomeScreen>
|
|||
.indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount);
|
||||
final canClose = activeIndex != -1;
|
||||
|
||||
final drawer = ZoomDrawer(
|
||||
controller: _zoomDrawerController,
|
||||
menuScreen: Builder(builder: (context) {
|
||||
final zoomDrawer = ZoomDrawer.of(context);
|
||||
zoomDrawer!.stateNotifier.addListener(() {
|
||||
if (zoomDrawer.isOpen()) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
});
|
||||
return const DrawerMenu();
|
||||
}),
|
||||
mainScreen: Provider<ZoomDrawerController>.value(
|
||||
value: _zoomDrawerController,
|
||||
child: Builder(builder: _buildAccountPageView)),
|
||||
borderRadius: 0,
|
||||
angle: 0,
|
||||
openCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
closeCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
menuScreenTapClose: canClose,
|
||||
mainScreenTapClose: canClose,
|
||||
disableDragGesture: !canClose,
|
||||
mainScreenScale: .25,
|
||||
slideWidth: min(360, MediaQuery.of(context).size.width * 0.9),
|
||||
);
|
||||
|
||||
final drawerWithAvoider =
|
||||
isWeb ? drawer : KeyboardAvoider(curve: Curves.ease, child: drawer);
|
||||
|
||||
return DefaultTextStyle(
|
||||
style: theme.textTheme.bodySmall!,
|
||||
child: KeyboardAvoider(
|
||||
curve: Curves.ease,
|
||||
child: ZoomDrawer(
|
||||
controller: _zoomDrawerController,
|
||||
menuScreen: Builder(builder: (context) {
|
||||
final zoomDrawer = ZoomDrawer.of(context);
|
||||
zoomDrawer!.stateNotifier.addListener(() {
|
||||
if (zoomDrawer.isOpen()) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
});
|
||||
return const DrawerMenu();
|
||||
}),
|
||||
mainScreen: Provider<ZoomDrawerController>.value(
|
||||
value: _zoomDrawerController,
|
||||
child: Builder(builder: _buildAccountPageView)),
|
||||
borderRadius: 0,
|
||||
angle: 0,
|
||||
openCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
closeCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
menuScreenTapClose: canClose,
|
||||
mainScreenTapClose: canClose,
|
||||
disableDragGesture: !canClose,
|
||||
mainScreenScale: .25,
|
||||
slideWidth: min(360, MediaQuery.of(context).size.width * 0.9),
|
||||
)));
|
||||
style: theme.textTheme.bodySmall!, child: drawerWithAvoider);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../settings/settings.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import '../notifications.dart';
|
||||
|
||||
const String formFieldDisplayBetaWarning = 'displayBetaWarning';
|
||||
const String formFieldEnableBadge = 'enableBadge';
|
||||
const String formFieldEnableNotifications = 'enableNotifications';
|
||||
const String formFieldMessageNotificationContent = 'messageNotificationContent';
|
||||
const String formFieldInvitationAcceptMode = 'invitationAcceptMode';
|
||||
const String formFieldInvitationAcceptSound = 'invitationAcceptSound';
|
||||
const String formFieldMessageReceivedMode = 'messageReceivedMode';
|
||||
const String formFieldMessageReceivedSound = 'messageReceivedSound';
|
||||
const String formFieldMessageSentSound = 'messageSentSound';
|
||||
|
||||
Widget buildSettingsPageNotificationPreferences(
|
||||
{required BuildContext context, required void Function() onChanged}) {
|
||||
{required BuildContext context}) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
|
@ -33,7 +22,6 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
final newPrefs = preferencesRepository.value
|
||||
.copyWith(notificationsPreference: newNotificationsPreference);
|
||||
await preferencesRepository.set(newPrefs);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<NotificationMode>> notificationModeItems() {
|
||||
|
@ -54,9 +42,10 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
enabled: x.$2,
|
||||
child: Text(
|
||||
x.$3,
|
||||
style: textTheme.labelSmall,
|
||||
softWrap: false,
|
||||
style: textTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
)));
|
||||
).fit(fit: BoxFit.scaleDown)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -77,7 +66,8 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
enabled: x.$2,
|
||||
child: Text(
|
||||
x.$3,
|
||||
style: textTheme.labelSmall,
|
||||
softWrap: false,
|
||||
style: textTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
)));
|
||||
}
|
||||
|
@ -110,7 +100,8 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
enabled: x.$2,
|
||||
child: Text(
|
||||
x.$3,
|
||||
style: textTheme.labelSmall,
|
||||
softWrap: false,
|
||||
style: textTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
)));
|
||||
}
|
||||
|
@ -127,66 +118,45 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
// Display Beta Warning
|
||||
FormBuilderCheckbox(
|
||||
name: formFieldDisplayBetaWarning,
|
||||
title: Text(translate('settings_page.display_beta_warning'),
|
||||
style: textTheme.labelMedium),
|
||||
initialValue: notificationsPreference.displayBetaWarning,
|
||||
StyledCheckbox(
|
||||
label: translate('settings_page.display_beta_warning'),
|
||||
value: notificationsPreference.displayBetaWarning,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(displayBetaWarning: value);
|
||||
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
}),
|
||||
// Enable Badge
|
||||
FormBuilderCheckbox(
|
||||
name: formFieldEnableBadge,
|
||||
title: Text(translate('settings_page.enable_badge'),
|
||||
style: textTheme.labelMedium),
|
||||
initialValue: notificationsPreference.enableBadge,
|
||||
StyledCheckbox(
|
||||
label: translate('settings_page.enable_badge'),
|
||||
value: notificationsPreference.enableBadge,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(enableBadge: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
}),
|
||||
// Enable Notifications
|
||||
FormBuilderCheckbox(
|
||||
name: formFieldEnableNotifications,
|
||||
title: Text(translate('settings_page.enable_notifications'),
|
||||
style: textTheme.labelMedium),
|
||||
initialValue: notificationsPreference.enableNotifications,
|
||||
StyledCheckbox(
|
||||
label: translate('settings_page.enable_notifications'),
|
||||
value: notificationsPreference.enableNotifications,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(enableNotifications: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
}),
|
||||
|
||||
FormBuilderDropdown(
|
||||
name: formFieldMessageNotificationContent,
|
||||
isDense: false,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('settings_page.message_notification_content')),
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.messageNotificationContent,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference.copyWith(
|
||||
messageNotificationContent: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
StyledDropdown<MessageNotificationContent>(
|
||||
items: messageNotificationContentItems(),
|
||||
).paddingLTRB(0, 4, 0, 4),
|
||||
value: notificationsPreference.messageNotificationContent,
|
||||
decoratorLabel: translate('settings_page.message_notification_content'),
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(messageNotificationContent: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)),
|
||||
|
||||
// Notifications
|
||||
Table(
|
||||
|
@ -199,95 +169,85 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
color: scale.primaryScale.border,
|
||||
decorationColor: scale.primaryScale.border,
|
||||
decoration: TextDecoration.underline))
|
||||
.paddingAll(8),
|
||||
.paddingAll(8.scaled(context)),
|
||||
Text(translate('settings_page.delivery'),
|
||||
textAlign: TextAlign.center,
|
||||
style: textTheme.titleMedium!.copyWith(
|
||||
color: scale.primaryScale.border,
|
||||
decorationColor: scale.primaryScale.border,
|
||||
decoration: TextDecoration.underline))
|
||||
.paddingAll(8),
|
||||
.paddingAll(8.scaled(context)),
|
||||
Text(translate('settings_page.sound'),
|
||||
textAlign: TextAlign.center,
|
||||
style: textTheme.titleMedium!.copyWith(
|
||||
color: scale.primaryScale.border,
|
||||
decorationColor: scale.primaryScale.border,
|
||||
decoration: TextDecoration.underline))
|
||||
.paddingAll(8),
|
||||
.paddingAll(8.scaled(context)),
|
||||
]),
|
||||
TableRow(children: [
|
||||
// Invitation accepted
|
||||
Text(
|
||||
textAlign: TextAlign.right,
|
||||
translate('settings_page.invitation_accepted'))
|
||||
.paddingAll(8),
|
||||
FormBuilderDropdown(
|
||||
name: formFieldInvitationAcceptMode,
|
||||
isDense: false,
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.onInvitationAcceptedMode,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(onInvitationAcceptedMode: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
.paddingAll(4.scaled(context)),
|
||||
StyledDropdown<NotificationMode>(
|
||||
items: notificationModeItems(),
|
||||
).paddingAll(4),
|
||||
FormBuilderDropdown(
|
||||
name: formFieldInvitationAcceptSound,
|
||||
isDense: false,
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.onInvitationAcceptedSound,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(onInvitationAcceptedSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
value: notificationsPreference.onInvitationAcceptedMode,
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(
|
||||
onInvitationAcceptedMode: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingAll(4.scaled(context)),
|
||||
StyledDropdown<SoundEffect>(
|
||||
items: soundEffectItems(),
|
||||
).paddingLTRB(4, 4, 0, 4)
|
||||
value: notificationsPreference.onInvitationAcceptedSound,
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(
|
||||
onInvitationAcceptedSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingLTRB(
|
||||
4.scaled(context), 4.scaled(context), 0, 4.scaled(context))
|
||||
]),
|
||||
// Message received
|
||||
TableRow(children: [
|
||||
Text(
|
||||
textAlign: TextAlign.right,
|
||||
translate('settings_page.message_received'))
|
||||
.paddingAll(8),
|
||||
FormBuilderDropdown(
|
||||
name: formFieldMessageReceivedMode,
|
||||
isDense: false,
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.onMessageReceivedMode,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(onMessageReceivedMode: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
.paddingAll(4.scaled(context)),
|
||||
StyledDropdown<NotificationMode>(
|
||||
items: notificationModeItems(),
|
||||
value: notificationsPreference.onMessageReceivedMode,
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(
|
||||
onMessageReceivedMode: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingAll(4),
|
||||
FormBuilderDropdown(
|
||||
name: formFieldMessageReceivedSound,
|
||||
isDense: false,
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.onMessageReceivedSound,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(onMessageReceivedSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
StyledDropdown<SoundEffect>(
|
||||
items: soundEffectItems(),
|
||||
).paddingLTRB(4, 4, 0, 4)
|
||||
value: notificationsPreference.onMessageReceivedSound,
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(
|
||||
onMessageReceivedSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingLTRB(
|
||||
4.scaled(context), 4.scaled(context), 0, 4.scaled(context))
|
||||
]),
|
||||
|
||||
// Message sent
|
||||
|
@ -295,25 +255,23 @@ Widget buildSettingsPageNotificationPreferences(
|
|||
Text(
|
||||
textAlign: TextAlign.right,
|
||||
translate('settings_page.message_sent'))
|
||||
.paddingAll(8),
|
||||
.paddingAll(4.scaled(context)),
|
||||
const SizedBox.shrink(),
|
||||
FormBuilderDropdown(
|
||||
name: formFieldMessageSentSound,
|
||||
isDense: false,
|
||||
enabled: notificationsPreference.enableNotifications,
|
||||
initialValue: notificationsPreference.onMessageSentSound,
|
||||
onChanged: (value) async {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final newNotificationsPreference = notificationsPreference
|
||||
.copyWith(onMessageSentSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
StyledDropdown<SoundEffect>(
|
||||
items: soundEffectItems(),
|
||||
).paddingLTRB(4, 4, 0, 4)
|
||||
value: notificationsPreference.onMessageSentSound,
|
||||
onChanged: !notificationsPreference.enableNotifications
|
||||
? null
|
||||
: (value) async {
|
||||
final newNotificationsPreference =
|
||||
notificationsPreference.copyWith(
|
||||
onMessageSentSound: value);
|
||||
await updatePreferences(newNotificationsPreference);
|
||||
},
|
||||
).paddingLTRB(
|
||||
4.scaled(context), 4.scaled(context), 0, 4.scaled(context))
|
||||
]),
|
||||
])
|
||||
]).paddingAll(8),
|
||||
]).paddingAll(8.scaled(context)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../keyboard_shortcuts.dart';
|
||||
import '../../notifications/notifications.dart';
|
||||
import '../../settings/settings.dart';
|
||||
import '../../theme/theme.dart';
|
||||
|
||||
class RouterShell extends StatelessWidget {
|
||||
|
@ -10,7 +12,13 @@ class RouterShell extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) => PopControl(
|
||||
dismissible: false,
|
||||
child: NotificationsWidget(child: KeyboardShortcuts(child: _child)));
|
||||
child: AsyncBlocBuilder<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;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ sealed class LockPreference with _$LockPreference {
|
|||
factory LockPreference.fromJson(dynamic json) =>
|
||||
_$LockPreferenceFromJson(json as Map<String, dynamic>);
|
||||
|
||||
static const LockPreference defaults = LockPreference();
|
||||
static const defaults = LockPreference();
|
||||
}
|
||||
|
||||
// Theme supports multiple translations
|
||||
|
@ -49,5 +49,5 @@ sealed class Preferences with _$Preferences {
|
|||
factory Preferences.fromJson(dynamic json) =>
|
||||
_$PreferencesFromJson(json as Map<String, dynamic>);
|
||||
|
||||
static const Preferences defaults = Preferences();
|
||||
static const defaults = Preferences();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
|
@ -11,62 +10,53 @@ import '../theme/theme.dart';
|
|||
import '../veilid_processor/veilid_processor.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
SettingsPageState createState() => SettingsPageState();
|
||||
}
|
||||
|
||||
class SettingsPageState extends State<SettingsPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
static const String formFieldTheme = 'theme';
|
||||
static const String formFieldBrightness = 'brightness';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
AsyncBlocBuilder<PreferencesCubit, Preferences>(
|
||||
builder: (context, state) => ThemeSwitcher.withTheme(
|
||||
builder: (_, switcher, theme) => StyledScaffold(
|
||||
builder: (_, switcher, theme) => StyledScaffold(
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(translate('settings_page.titlebar')),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
iconSize: 24.scaled(context),
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () => GoRouterHelper(context).pop(),
|
||||
),
|
||||
actions: <Widget>[
|
||||
const SignalStrengthMeterWidget()
|
||||
.paddingLTRB(16, 0, 16, 0),
|
||||
]),
|
||||
body: ThemeSwitchingArea(
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
buildSettingsPageColorPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
onChanged: () => setState(() {}))
|
||||
.paddingLTRB(0, 8, 0, 0),
|
||||
buildSettingsPageBrightnessPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
onChanged: () => setState(() {})),
|
||||
buildSettingsPageWallpaperPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
onChanged: () => setState(() {})),
|
||||
buildSettingsPageNotificationPreferences(
|
||||
context: context,
|
||||
onChanged: () => setState(() {})),
|
||||
].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(8).scaled(context),
|
||||
children: [
|
||||
buildSettingsPageColorPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
),
|
||||
).paddingSymmetric(horizontal: 8, vertical: 8),
|
||||
))));
|
||||
buildSettingsPageBrightnessPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
),
|
||||
buildSettingsPageDisplayScalePreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
),
|
||||
buildSettingsPageWallpaperPreferences(
|
||||
context: context,
|
||||
switcher: switcher,
|
||||
),
|
||||
buildSettingsPageNotificationPreferences(
|
||||
context: context,
|
||||
),
|
||||
]
|
||||
.map((x) => x.paddingLTRB(0, 0, 0, 8.scaled(context)))
|
||||
.toList(),
|
||||
).paddingSymmetric(vertical: 4.scaled(context)),
|
||||
).paddingSymmetric(
|
||||
horizontal: 8.scaled(context), vertical: 8.scaled(context)),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -308,6 +308,13 @@ ThemeData contrastGenerator({
|
|||
side: elementBorderWidgetStateProperty(),
|
||||
backgroundColor: elementBackgroundWidgetStateProperty()));
|
||||
|
||||
final sliderTheme = SliderThemeData.fromPrimaryColors(
|
||||
primaryColor: scheme.primaryScale.borderText,
|
||||
primaryColorDark: scheme.primaryScale.border,
|
||||
primaryColorLight: scheme.primaryScale.border,
|
||||
valueIndicatorTextStyle: textTheme.labelMedium!
|
||||
.copyWith(color: scheme.primaryScale.borderText));
|
||||
|
||||
final themeData = baseThemeData.copyWith(
|
||||
// chipTheme: baseThemeData.chipTheme.copyWith(
|
||||
// backgroundColor: scaleScheme.primaryScale.elementBackground,
|
||||
|
@ -316,6 +323,7 @@ ThemeData contrastGenerator({
|
|||
// checkmarkColor: scaleScheme.primaryScale.border,
|
||||
// side: BorderSide(color: scaleScheme.primaryScale.border)),
|
||||
elevatedButtonTheme: elevatedButtonTheme,
|
||||
sliderTheme: sliderTheme,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: scheme.primaryScale.appText,
|
||||
selectionColor: scheme.primaryScale.appText.withAlpha(0x7F),
|
||||
|
|
|
@ -132,6 +132,13 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
|||
iconColor: elementColorWidgetStateProperty(),
|
||||
));
|
||||
|
||||
final sliderTheme = SliderThemeData.fromPrimaryColors(
|
||||
primaryColor: scheme.primaryScale.hoverBorder,
|
||||
primaryColorDark: scheme.primaryScale.border,
|
||||
primaryColorLight: scheme.primaryScale.border,
|
||||
valueIndicatorTextStyle: textTheme.labelMedium!
|
||||
.copyWith(color: scheme.primaryScale.borderText));
|
||||
|
||||
final themeData = baseThemeData.copyWith(
|
||||
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
|
@ -183,6 +190,7 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
|||
elevatedButtonTheme: elevatedButtonTheme,
|
||||
inputDecorationTheme:
|
||||
ScaleInputDecoratorTheme(scheme, config, textTheme),
|
||||
sliderTheme: sliderTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[scheme, config, this]);
|
||||
|
||||
return themeData;
|
||||
|
|
|
@ -61,7 +61,7 @@ sealed class ThemePreferences with _$ThemePreferences {
|
|||
factory ThemePreferences.fromJson(dynamic json) =>
|
||||
_$ThemePreferencesFromJson(json as Map<String, dynamic>);
|
||||
|
||||
static const ThemePreferences defaults = ThemePreferences();
|
||||
static const defaults = ThemePreferences();
|
||||
}
|
||||
|
||||
extension ThemePreferencesExt on ThemePreferences {
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
|
||||
class AvatarWidget extends StatelessWidget {
|
||||
const AvatarWidget({
|
||||
required String name,
|
||||
required double size,
|
||||
required Color borderColor,
|
||||
required Color foregroundColor,
|
||||
required Color backgroundColor,
|
||||
required ScaleConfig scaleConfig,
|
||||
required TextStyle textStyle,
|
||||
super.key,
|
||||
ImageProvider<Object>? imageProvider,
|
||||
}) : _name = name,
|
||||
_size = size,
|
||||
_borderColor = borderColor,
|
||||
_foregroundColor = foregroundColor,
|
||||
_backgroundColor = backgroundColor,
|
||||
_scaleConfig = scaleConfig,
|
||||
_textStyle = textStyle,
|
||||
_imageProvider = imageProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final abbrev = _name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join();
|
||||
late final String shortname;
|
||||
if (abbrev.length >= 3) {
|
||||
shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1];
|
||||
} else {
|
||||
shortname = abbrev;
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: _size,
|
||||
width: _size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: _borderColor,
|
||||
width: 1 * (_size ~/ 32 + 1),
|
||||
strokeAlign: BorderSide.strokeAlignOutside)),
|
||||
child: AvatarImage(
|
||||
//size: 32,
|
||||
backgroundImage: _imageProvider,
|
||||
backgroundColor:
|
||||
_scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders
|
||||
? _foregroundColor
|
||||
: _backgroundColor,
|
||||
child: Text(
|
||||
shortname.isNotEmpty ? shortname : '?',
|
||||
softWrap: false,
|
||||
style: _textStyle.copyWith(
|
||||
color: _scaleConfig.useVisualIndicators &&
|
||||
!_scaleConfig.preferBorders
|
||||
? _backgroundColor
|
||||
: _foregroundColor,
|
||||
),
|
||||
).fit().paddingAll(_size / 16)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
final String _name;
|
||||
final double _size;
|
||||
final Color _borderColor;
|
||||
final Color _foregroundColor;
|
||||
final Color _backgroundColor;
|
||||
final ScaleConfig _scaleConfig;
|
||||
final TextStyle _textStyle;
|
||||
final ImageProvider<Object>? _imageProvider;
|
||||
}
|
|
@ -32,7 +32,7 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
|
|||
final passwordController = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool _passwordVisible = false;
|
||||
var _passwordVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -47,7 +47,6 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
|
|||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../settings/settings.dart';
|
||||
import '../models/models.dart';
|
||||
import '../../../settings/settings.dart';
|
||||
import '../../models/models.dart';
|
||||
import '../views.dart';
|
||||
|
||||
const String formFieldBrightness = 'brightness';
|
||||
|
||||
List<DropdownMenuItem<dynamic>> _getBrightnessDropdownItems() {
|
||||
List<DropdownMenuItem<BrightnessPreference>> _getBrightnessDropdownItems() {
|
||||
const brightnessPrefs = BrightnessPreference.values;
|
||||
final brightnessNames = {
|
||||
BrightnessPreference.system: translate('brightness.system'),
|
||||
|
@ -22,25 +20,21 @@ List<DropdownMenuItem<dynamic>> _getBrightnessDropdownItems() {
|
|||
}
|
||||
|
||||
Widget buildSettingsPageBrightnessPreferences(
|
||||
{required BuildContext context,
|
||||
required void Function() onChanged,
|
||||
required ThemeSwitcherState switcher}) {
|
||||
{required BuildContext context, required ThemeSwitcherState switcher}) {
|
||||
final preferencesRepository = PreferencesRepository.instance;
|
||||
final themePreferences = preferencesRepository.value.themePreference;
|
||||
return FormBuilderDropdown(
|
||||
name: formFieldBrightness,
|
||||
decoration: InputDecoration(
|
||||
label: Text(translate('settings_page.brightness_mode'))),
|
||||
|
||||
return StyledDropdown<BrightnessPreference>(
|
||||
items: _getBrightnessDropdownItems(),
|
||||
initialValue: themePreferences.brightnessPreference,
|
||||
value: themePreferences.brightnessPreference,
|
||||
decoratorLabel: translate('settings_page.brightness_mode'),
|
||||
onChanged: (value) async {
|
||||
final newThemePrefs = themePreferences.copyWith(
|
||||
brightnessPreference: value as BrightnessPreference);
|
||||
final newThemePrefs =
|
||||
themePreferences.copyWith(brightnessPreference: value);
|
||||
final newPrefs = preferencesRepository.value
|
||||
.copyWith(themePreference: newThemePrefs);
|
||||
|
||||
await preferencesRepository.set(newPrefs);
|
||||
switcher.changeTheme(theme: newThemePrefs.themeData());
|
||||
onChanged();
|
||||
});
|
||||
}
|
|
@ -1,16 +1,12 @@
|
|||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../settings/settings.dart';
|
||||
import '../models/models.dart';
|
||||
import '../../../settings/settings.dart';
|
||||
import '../../models/models.dart';
|
||||
import '../views.dart';
|
||||
|
||||
const String formFieldTheme = 'theme';
|
||||
const String _kSwitchTheme = 'switchTheme';
|
||||
|
||||
List<DropdownMenuItem<dynamic>> _getThemeDropdownItems() {
|
||||
List<DropdownMenuItem<ColorPreference>> _getThemeDropdownItems() {
|
||||
const colorPrefs = ColorPreference.values;
|
||||
final colorNames = {
|
||||
ColorPreference.scarlet: translate('themes.scarlet'),
|
||||
|
@ -34,27 +30,20 @@ List<DropdownMenuItem<dynamic>> _getThemeDropdownItems() {
|
|||
}
|
||||
|
||||
Widget buildSettingsPageColorPreferences(
|
||||
{required BuildContext context,
|
||||
required void Function() onChanged,
|
||||
required ThemeSwitcherState switcher}) {
|
||||
{required BuildContext context, required ThemeSwitcherState switcher}) {
|
||||
final preferencesRepository = PreferencesRepository.instance;
|
||||
final themePreferences = preferencesRepository.value.themePreference;
|
||||
return FormBuilderDropdown(
|
||||
name: formFieldTheme,
|
||||
decoration:
|
||||
InputDecoration(label: Text(translate('settings_page.color_theme'))),
|
||||
items: _getThemeDropdownItems(),
|
||||
initialValue: themePreferences.colorPreference,
|
||||
onChanged: (value) {
|
||||
singleFuture(_kSwitchTheme, () async {
|
||||
final newThemePrefs = themePreferences.copyWith(
|
||||
colorPreference: value as ColorPreference);
|
||||
final newPrefs = preferencesRepository.value
|
||||
.copyWith(themePreference: newThemePrefs);
|
||||
|
||||
await preferencesRepository.set(newPrefs);
|
||||
switcher.changeTheme(theme: newThemePrefs.themeData());
|
||||
onChanged();
|
||||
});
|
||||
return StyledDropdown<ColorPreference>(
|
||||
items: _getThemeDropdownItems(),
|
||||
value: themePreferences.colorPreference,
|
||||
decoratorLabel: translate('settings_page.color_theme'),
|
||||
onChanged: (value) async {
|
||||
final newThemePrefs = themePreferences.copyWith(colorPreference: value);
|
||||
final newPrefs = preferencesRepository.value
|
||||
.copyWith(themePreference: newThemePrefs);
|
||||
|
||||
await preferencesRepository.set(newPrefs);
|
||||
switcher.changeTheme(theme: newThemePrefs.themeData());
|
||||
});
|
||||
}
|
109
lib/theme/views/preferences/display_scale_preferences.dart
Normal file
109
lib/theme/views/preferences/display_scale_preferences.dart
Normal 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 / 2),
|
||||
1 / (1 + 1 / 3),
|
||||
1 / (1 + 1 / 4),
|
||||
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]);
|
||||
}
|
||||
}
|
4
lib/theme/views/preferences/preferences.dart
Normal file
4
lib/theme/views/preferences/preferences.dart
Normal file
|
@ -0,0 +1,4 @@
|
|||
export 'brightness_preferences.dart';
|
||||
export 'color_preferences.dart';
|
||||
export 'display_scale_preferences.dart';
|
||||
export 'wallpaper_preferences.dart';
|
25
lib/theme/views/preferences/wallpaper_preferences.dart
Normal file
25
lib/theme/views/preferences/wallpaper_preferences.dart
Normal 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());
|
||||
});
|
||||
}
|
|
@ -3,6 +3,10 @@ import 'package:flutter/material.dart';
|
|||
|
||||
final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
|
||||
final isiOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
|
||||
final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS;
|
||||
final isWindows = !kIsWeb && defaultTargetPlatform == TargetPlatform.windows;
|
||||
final isLinux = !kIsWeb && defaultTargetPlatform == TargetPlatform.linux;
|
||||
|
||||
final isMobile = !kIsWeb &&
|
||||
(defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.android);
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:rflutter_alert/rflutter_alert.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../../theme.dart';
|
||||
|
||||
AlertStyle _alertStyle(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
@ -186,6 +186,7 @@ Future<void> showAlertWidgetModal(
|
|||
child: Text(
|
||||
translate('button.ok'),
|
||||
style: _buttonTextStyle(context),
|
||||
softWrap: true,
|
||||
),
|
||||
)
|
||||
],
|
77
lib/theme/views/styled_widgets/styled_avatar.dart
Normal file
77
lib/theme/views/styled_widgets/styled_avatar.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../theme.dart';
|
||||
|
||||
class StyledAvatar extends StatelessWidget {
|
||||
const StyledAvatar({
|
||||
required String name,
|
||||
required double size,
|
||||
bool enabled = true,
|
||||
super.key,
|
||||
ImageProvider<Object>? imageProvider,
|
||||
}) : _name = name,
|
||||
_size = size,
|
||||
_imageProvider = imageProvider,
|
||||
_enabled = enabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scaleTheme = Theme.of(context).extension<ScaleTheme>()!;
|
||||
|
||||
final borderColor = scaleTheme.config.useVisualIndicators
|
||||
? scaleTheme.scheme.primaryScale.primaryText
|
||||
: scaleTheme.scheme.primaryScale.subtleBorder;
|
||||
final foregroundColor = !_enabled
|
||||
? scaleTheme.scheme.grayScale.primaryText
|
||||
: scaleTheme.scheme.primaryScale.calloutText;
|
||||
final backgroundColor = !_enabled
|
||||
? scaleTheme.scheme.grayScale.primary
|
||||
: scaleTheme.scheme.primaryScale.calloutBackground;
|
||||
final scaleConfig = scaleTheme.config;
|
||||
final textStyle = theme.textTheme.titleLarge!.copyWith(fontSize: _size / 2);
|
||||
|
||||
final abbrev = _name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join();
|
||||
late final String shortname;
|
||||
if (abbrev.length >= 3) {
|
||||
shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1];
|
||||
} else {
|
||||
shortname = abbrev;
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: _size,
|
||||
width: _size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: !scaleConfig.useVisualIndicators
|
||||
? null
|
||||
: Border.all(
|
||||
color: borderColor,
|
||||
width: 1 * (_size ~/ 16 + 1),
|
||||
strokeAlign: BorderSide.strokeAlignOutside)),
|
||||
child: AvatarImage(
|
||||
backgroundImage: _imageProvider,
|
||||
backgroundColor: scaleConfig.useVisualIndicators
|
||||
? foregroundColor
|
||||
: backgroundColor,
|
||||
child: Text(
|
||||
shortname.isNotEmpty ? shortname : '?',
|
||||
softWrap: false,
|
||||
textScaler: MediaQuery.of(context).textScaler,
|
||||
style: textStyle.copyWith(
|
||||
color: scaleConfig.useVisualIndicators
|
||||
? backgroundColor
|
||||
: foregroundColor,
|
||||
),
|
||||
).paddingAll(4.scaled(context)).fit(fit: BoxFit.scaleDown)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
final String _name;
|
||||
final double _size;
|
||||
final ImageProvider<Object>? _imageProvider;
|
||||
final bool _enabled;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../../theme.dart';
|
||||
|
||||
class OptionBox extends StatelessWidget {
|
||||
const OptionBox(
|
||||
class StyledButtonBox extends StatelessWidget {
|
||||
const StyledButtonBox(
|
||||
{required String instructions,
|
||||
required IconData buttonIcon,
|
||||
required String buttonText,
|
||||
|
@ -41,12 +41,15 @@ class OptionBox extends StatelessWidget {
|
|||
onPressed: _onClick,
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(_buttonIcon,
|
||||
size: 24, color: scale.primaryScale.appText)
|
||||
.paddingLTRB(0, 8, 8, 8),
|
||||
size: 24.scaled(context),
|
||||
color: scale.primaryScale.appText)
|
||||
.paddingLTRB(0, 8.scaled(context),
|
||||
8.scaled(context), 8.scaled(context)),
|
||||
Text(textAlign: TextAlign.center, _buttonText)
|
||||
])).paddingLTRB(0, 12, 0, 0).toCenter()
|
||||
]).paddingAll(12))
|
||||
.paddingLTRB(24, 0, 24, 12);
|
||||
])).paddingLTRB(0, 12.scaled(context), 0, 0).toCenter()
|
||||
]).paddingAll(12.scaled(context)))
|
||||
.paddingLTRB(
|
||||
24.scaled(context), 0, 24.scaled(context), 12.scaled(context));
|
||||
}
|
||||
|
||||
final String _instructions;
|
63
lib/theme/views/styled_widgets/styled_checkbox.dart
Normal file
63
lib/theme/views/styled_widgets/styled_checkbox.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
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: [
|
||||
Transform.scale(
|
||||
scale: 1.scaled(context),
|
||||
child: Checkbox(
|
||||
value: _value,
|
||||
onChanged: _onChanged == null
|
||||
? null
|
||||
: (value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
singleFuture((this, _kStyledCheckboxChanged), () async {
|
||||
await _onChanged(value);
|
||||
});
|
||||
})),
|
||||
Text(_label, style: textStyle).paddingAll(4.scaled(context)),
|
||||
]);
|
||||
|
||||
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;
|
||||
}
|
|
@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../../theme.dart';
|
||||
|
||||
class StyledDialog extends StatelessWidget {
|
||||
const StyledDialog({required this.title, required this.child, super.key});
|
59
lib/theme/views/styled_widgets/styled_dropdown.dart
Normal file
59
lib/theme/views/styled_widgets/styled_dropdown.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
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,
|
||||
style: theme.textTheme.labelLarge,
|
||||
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;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../../theme.dart';
|
||||
|
||||
class StyledScaffold extends StatelessWidget {
|
||||
const StyledScaffold({required this.appBar, required this.body, super.key});
|
|
@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../../theme.dart';
|
||||
|
||||
class SliderTileAction {
|
||||
const SliderTileAction({
|
||||
class SlideTileAction {
|
||||
const SlideTileAction({
|
||||
required this.actionScale,
|
||||
required this.onPressed,
|
||||
this.key,
|
||||
|
@ -20,8 +20,8 @@ class SliderTileAction {
|
|||
final SlidableActionCallback? onPressed;
|
||||
}
|
||||
|
||||
class SliderTile extends StatelessWidget {
|
||||
const SliderTile(
|
||||
class StyledSlideTile extends StatelessWidget {
|
||||
const StyledSlideTile(
|
||||
{required this.disabled,
|
||||
required this.selected,
|
||||
required this.tileScale,
|
||||
|
@ -38,8 +38,8 @@ class SliderTile extends StatelessWidget {
|
|||
final bool disabled;
|
||||
final bool selected;
|
||||
final ScaleKind tileScale;
|
||||
final List<SliderTileAction> endActions;
|
||||
final List<SliderTileAction> startActions;
|
||||
final List<SlideTileAction> endActions;
|
||||
final List<SlideTileAction> startActions;
|
||||
final GestureTapCallback? onTap;
|
||||
final GestureTapCallback? onDoubleTap;
|
||||
final Widget? leading;
|
||||
|
@ -54,8 +54,8 @@ class SliderTile extends StatelessWidget {
|
|||
..add(DiagnosticsProperty<bool>('disabled', disabled))
|
||||
..add(DiagnosticsProperty<bool>('selected', selected))
|
||||
..add(DiagnosticsProperty<ScaleKind>('tileScale', tileScale))
|
||||
..add(IterableProperty<SliderTileAction>('endActions', endActions))
|
||||
..add(IterableProperty<SliderTileAction>('startActions', startActions))
|
||||
..add(IterableProperty<SlideTileAction>('endActions', endActions))
|
||||
..add(IterableProperty<SlideTileAction>('startActions', startActions))
|
||||
..add(ObjectFlagProperty<GestureTapCallback?>.has('onTap', onTap))
|
||||
..add(DiagnosticsProperty<Widget?>('leading', leading))
|
||||
..add(StringProperty('title', title))
|
||||
|
@ -66,7 +66,6 @@ class SliderTile extends StatelessWidget {
|
|||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scaleTheme = theme.extension<ScaleTheme>()!;
|
||||
|
@ -91,12 +90,13 @@ class SliderTile extends StatelessWidget {
|
|||
selected: true,
|
||||
scaleKind: a.actionScale);
|
||||
return SlidableAction(
|
||||
onPressed: disabled ? null : a.onPressed,
|
||||
backgroundColor: scaleActionTheme.backgroundColor,
|
||||
foregroundColor: scaleActionTheme.textColor,
|
||||
icon: subtitle.isEmpty ? a.icon : null,
|
||||
label: a.label,
|
||||
padding: const EdgeInsets.all(2));
|
||||
onPressed: disabled ? null : a.onPressed,
|
||||
backgroundColor: scaleActionTheme.backgroundColor,
|
||||
foregroundColor: scaleActionTheme.textColor,
|
||||
icon: subtitle.isEmpty ? a.icon : null,
|
||||
label: a.label,
|
||||
padding: const EdgeInsets.all(2).scaled(context),
|
||||
);
|
||||
}).toList()),
|
||||
startActionPane: startActions.isEmpty
|
||||
? null
|
||||
|
@ -109,17 +109,18 @@ class SliderTile extends StatelessWidget {
|
|||
scaleKind: a.actionScale);
|
||||
|
||||
return SlidableAction(
|
||||
onPressed: disabled ? null : a.onPressed,
|
||||
backgroundColor: scaleActionTheme.backgroundColor,
|
||||
foregroundColor: scaleActionTheme.textColor,
|
||||
icon: subtitle.isEmpty ? a.icon : null,
|
||||
label: a.label,
|
||||
padding: const EdgeInsets.all(2));
|
||||
onPressed: disabled ? null : a.onPressed,
|
||||
backgroundColor: scaleActionTheme.backgroundColor,
|
||||
foregroundColor: scaleActionTheme.textColor,
|
||||
icon: subtitle.isEmpty ? a.icon : null,
|
||||
label: a.label,
|
||||
padding: const EdgeInsets.all(2).scaled(context),
|
||||
);
|
||||
}).toList()),
|
||||
child: Padding(
|
||||
padding: scaleTheme.config.useVisualIndicators
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(0, 2, 0, 2),
|
||||
: const EdgeInsets.fromLTRB(0, 2, 0, 2).scaled(context),
|
||||
child: GestureDetector(
|
||||
onDoubleTap: onDoubleTap,
|
||||
child: ListTile(
|
||||
|
@ -131,7 +132,8 @@ class SliderTile extends StatelessWidget {
|
|||
softWrap: false,
|
||||
),
|
||||
subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
|
||||
minTileHeight: 52,
|
||||
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)
|
||||
.scaled(context),
|
||||
iconColor: scaleTileTheme.textColor,
|
||||
textColor: scaleTileTheme.textColor,
|
||||
leading:
|
79
lib/theme/views/styled_widgets/styled_slider.dart
Normal file
79
lib/theme/views/styled_widgets/styled_slider.dart
Normal 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;
|
||||
}
|
8
lib/theme/views/styled_widgets/styled_widgets.dart
Normal file
8
lib/theme/views/styled_widgets/styled_widgets.dart
Normal 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';
|
|
@ -1,16 +1,10 @@
|
|||
export 'avatar_widget.dart';
|
||||
export 'brightness_preferences.dart';
|
||||
export 'color_preferences.dart';
|
||||
export 'enter_password.dart';
|
||||
export 'enter_pin.dart';
|
||||
export 'option_box.dart';
|
||||
export 'pop_control.dart';
|
||||
export 'preferences/preferences.dart';
|
||||
export 'recovery_key_widget.dart';
|
||||
export 'responsive.dart';
|
||||
export 'scanner_error_widget.dart';
|
||||
export 'slider_tile.dart';
|
||||
export 'styled_alert.dart';
|
||||
export 'styled_dialog.dart';
|
||||
export 'styled_scaffold.dart';
|
||||
export 'wallpaper_preferences.dart';
|
||||
export 'styled_widgets/styled_button_box.dart';
|
||||
export 'styled_widgets/styled_widgets.dart';
|
||||
export 'widget_helpers.dart';
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -222,13 +222,16 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||
return Scaffold(
|
||||
backgroundColor: scale.primaryScale.border,
|
||||
appBar: DefaultAppBar(
|
||||
context: context,
|
||||
title: Text(translate('developer.title')),
|
||||
leading: IconButton(
|
||||
iconSize: 24.scaled(context),
|
||||
icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText),
|
||||
onPressed: () => GoRouterHelper(context).pop(),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
iconSize: 24.scaled(context),
|
||||
icon: const Icon(Icons.copy),
|
||||
color: scale.primaryScale.borderText,
|
||||
disabledColor: scale.primaryScale.borderText.withAlpha(0x3F),
|
||||
|
@ -238,6 +241,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||
await copySelection(context);
|
||||
}),
|
||||
IconButton(
|
||||
iconSize: 24.scaled(context),
|
||||
icon: const Icon(Icons.copy_all),
|
||||
color: scale.primaryScale.borderText,
|
||||
disabledColor: scale.primaryScale.borderText.withAlpha(0x3F),
|
||||
|
@ -245,6 +249,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||
await copyAll(context);
|
||||
}),
|
||||
IconButton(
|
||||
iconSize: 24.scaled(context),
|
||||
icon: const Icon(Icons.clear_all),
|
||||
color: scale.primaryScale.borderText,
|
||||
disabledColor: scale.primaryScale.borderText.withAlpha(0x3F),
|
||||
|
@ -259,7 +264,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||
}
|
||||
}),
|
||||
SizedBox.fromSize(
|
||||
size: const Size(140, 48),
|
||||
size: Size(140.scaled(context), 48),
|
||||
child: CustomDropdown<LogLevelDropdownItem>(
|
||||
items: _logLevelDropdownItems,
|
||||
initialItem: _logLevelDropdownItems
|
||||
|
@ -300,6 +305,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||
Image.asset('assets/images/ellet.png'),
|
||||
TerminalView(globalDebugTerminal,
|
||||
textStyle: kDefaultTerminalStyle,
|
||||
textScaler: TextScaler.noScaling,
|
||||
controller: _terminalController,
|
||||
keyboardType: TextInputType.none,
|
||||
backgroundOpacity: _showEllet ? 0.75 : 1.0,
|
||||
|
|
|
@ -19,7 +19,7 @@ class SignalStrengthMeterWidget extends StatelessWidget {
|
|||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
const iconSize = 16.0;
|
||||
final iconSize = 16.0.scaled(context);
|
||||
|
||||
return BlocBuilder<ConnectionStateCubit,
|
||||
AsyncValue<ProcessorConnectionState>>(builder: (context, state) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue