add adaptive_scaffold
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImFilledButton extends StatelessWidget {
|
||||
const ImFilledButton({
|
||||
super.key,
|
||||
this.icon,
|
||||
this.onPressed,
|
||||
this.isDisabled = false,
|
||||
required this.label,
|
||||
}) : _tonal = false;
|
||||
|
||||
const ImFilledButton.tonal({
|
||||
super.key,
|
||||
this.icon,
|
||||
this.onPressed,
|
||||
this.isDisabled = false,
|
||||
required this.label,
|
||||
}) : _tonal = true;
|
||||
|
||||
/// Internal flag to switch between filled and tonal variant
|
||||
final bool _tonal;
|
||||
|
||||
/// Should disable the button
|
||||
final bool isDisabled;
|
||||
|
||||
/// Icon to display if [withIcon] is true
|
||||
final IconData? icon;
|
||||
|
||||
/// Action to perform on Button press
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Label to be displayed in the button
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_tonal) {
|
||||
if (icon != null) {
|
||||
return FilledButton.tonalIcon(
|
||||
onPressed: isDisabled ? null : onPressed,
|
||||
icon: Icon(icon),
|
||||
label: Text(label),
|
||||
);
|
||||
}
|
||||
|
||||
return FilledButton.tonal(
|
||||
onPressed: isDisabled ? null : onPressed,
|
||||
child: Text(label),
|
||||
);
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
return FilledButton.icon(
|
||||
onPressed: isDisabled ? null : onPressed,
|
||||
icon: Icon(icon),
|
||||
label: Text(label),
|
||||
);
|
||||
}
|
||||
|
||||
return FilledButton(
|
||||
onPressed: isDisabled ? null : onPressed,
|
||||
child: Text(label),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/presentation/components/input/text_form_field.widget.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class ImPasswordFormField extends StatefulWidget {
|
||||
const ImPasswordFormField({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.focusNode,
|
||||
this.label,
|
||||
this.hint,
|
||||
this.textInputAction,
|
||||
this.isDisabled = false,
|
||||
});
|
||||
|
||||
/// The [TextEditingController] passed to the underlying [TextFormField]
|
||||
final TextEditingController? controller;
|
||||
|
||||
/// Optional callback to receive changes
|
||||
final void Function(String?)? onChanged;
|
||||
|
||||
/// The [FocusNode] passed to the underlying [TextFormField]
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// Translation Key used as label
|
||||
final String? label;
|
||||
|
||||
/// Translation key used as hint
|
||||
final String? hint;
|
||||
|
||||
/// Type of the following action - go, next, enter, etc.
|
||||
final TextInputAction? textInputAction;
|
||||
|
||||
/// Flag to disable the [TextFormField]
|
||||
final bool isDisabled;
|
||||
|
||||
@override
|
||||
State createState() => _ImPasswordFormFieldState();
|
||||
}
|
||||
|
||||
class _ImPasswordFormFieldState extends State<ImPasswordFormField> {
|
||||
final showPassword = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
showPassword.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: showPassword,
|
||||
builder: (_, showPass, child) => ImTextFormField(
|
||||
controller: widget.controller,
|
||||
onChanged: widget.onChanged,
|
||||
shouldObscure: !showPass,
|
||||
hint: widget.hint,
|
||||
label: widget.label,
|
||||
focusNode: widget.focusNode,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => showPassword.value = !showPassword.value,
|
||||
icon: Icon(
|
||||
showPassword.value
|
||||
? Symbols.visibility_off_rounded
|
||||
: Symbols.visibility_rounded,
|
||||
),
|
||||
),
|
||||
autoFillHints: const [AutofillHints.password],
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
textInputAction: widget.textInputAction,
|
||||
isDisabled: widget.isDisabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
||||
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
||||
import 'package:immich_mobile/service_locator.dart';
|
||||
|
||||
class ImSwitchListTile<T> extends StatefulWidget {
|
||||
const ImSwitchListTile(
|
||||
this.setting, {
|
||||
super.key,
|
||||
this.fromAppSetting,
|
||||
this.toAppSetting,
|
||||
}) : assert(T == bool || (fromAppSetting != null && toAppSetting != null),
|
||||
"Setting is not a boolean and a from / to App setting is not provided");
|
||||
|
||||
final AppSetting<T> setting;
|
||||
|
||||
/// Converts the type T to a boolean to use in a switch
|
||||
final bool Function(T value)? fromAppSetting;
|
||||
|
||||
/// Converts the boolean back to the type T to be stored in the app setting. Return null to not update the DB but to
|
||||
/// retain the previous value
|
||||
final T? Function(bool state)? toAppSetting;
|
||||
|
||||
@override
|
||||
State createState() => _ImSwitchListTileState<T>();
|
||||
}
|
||||
|
||||
class _ImSwitchListTileState<T> extends State<ImSwitchListTile<T>> {
|
||||
// Actual switch list state
|
||||
late bool isEnabled;
|
||||
final AppSettingService _appSettingService = di();
|
||||
|
||||
Future<void> set(bool enabled) async {
|
||||
if (isEnabled == enabled) return;
|
||||
|
||||
final value = T != bool ? widget.toAppSetting!(enabled) : enabled as T;
|
||||
if (value != null &&
|
||||
await _appSettingService.setSetting(widget.setting, value) &&
|
||||
context.mounted) {
|
||||
setState(() {
|
||||
isEnabled = enabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final value = _appSettingService.getSetting(widget.setting);
|
||||
isEnabled = T != bool ? widget.fromAppSetting!(value) : value as bool;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
value: isEnabled,
|
||||
onChanged: (value) => set(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImTextButton extends StatelessWidget {
|
||||
const ImTextButton({
|
||||
super.key,
|
||||
this.icon,
|
||||
this.onPressed,
|
||||
this.isDisabled = false,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
/// Icon to display if [withIcon] is true
|
||||
final IconData? icon;
|
||||
|
||||
/// Flag to disable the button
|
||||
final bool isDisabled;
|
||||
|
||||
/// Action to perform on Button press
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Label to be displayed in the button
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (icon != null) {
|
||||
return TextButton.icon(
|
||||
onPressed: isDisabled ? null : onPressed,
|
||||
icon: Icon(icon),
|
||||
label: Text(label),
|
||||
);
|
||||
}
|
||||
|
||||
return TextButton(onPressed: onPressed, child: Text(label));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImTextFormField extends StatelessWidget {
|
||||
const ImTextFormField({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.onChanged,
|
||||
this.validator,
|
||||
this.shouldObscure = false,
|
||||
this.suffixIcon,
|
||||
this.label,
|
||||
this.hint,
|
||||
this.autoFillHints,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.isDisabled = false,
|
||||
this.onSubmitted,
|
||||
}) : assert(
|
||||
onSubmitted == null ||
|
||||
textInputAction == TextInputAction.next ||
|
||||
textInputAction == TextInputAction.previous,
|
||||
"onSubmitted provided when textInputAction is not next or pervious",
|
||||
);
|
||||
|
||||
/// The [TextEditingController] passed to the underlying [TextFormField]
|
||||
final TextEditingController? controller;
|
||||
|
||||
/// The [FocusNode] passed to the underlying [TextFormField]
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// Optional callback to validate input
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
/// Optional callback to receive changes
|
||||
final void Function(String?)? onChanged;
|
||||
|
||||
/// Optional flag to obscure texts
|
||||
final bool shouldObscure;
|
||||
|
||||
/// Icon Widget to display in the suffix
|
||||
final Widget? suffixIcon;
|
||||
|
||||
/// Translation Key used as label
|
||||
final String? label;
|
||||
|
||||
/// Translation key used as hint
|
||||
final String? hint;
|
||||
|
||||
/// Hints used by the auto-fill service
|
||||
final List<String>? autoFillHints;
|
||||
|
||||
/// Type of keyboard - Numberic / Alphanum
|
||||
final TextInputType? keyboardType;
|
||||
|
||||
/// Type of the following action - go, next, enter, etc.
|
||||
final TextInputAction? textInputAction;
|
||||
|
||||
/// Flag to disable the [TextFormField]
|
||||
final bool isDisabled;
|
||||
|
||||
/// Called on [TextInputAction.next] or [TextInputAction.previous]
|
||||
final void Function(String)? onSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
focusNode: focusNode,
|
||||
obscureText: shouldObscure,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
hintText: hint,
|
||||
suffixIcon: suffixIcon,
|
||||
),
|
||||
autofillHints: autoFillHints,
|
||||
keyboardType: keyboardType,
|
||||
textInputAction: textInputAction,
|
||||
readOnly: isDisabled,
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onFieldSubmitted: onSubmitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user