Add more components
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
// @dart=2.9
|
||||
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
@@ -8,7 +6,7 @@ class WebPage extends StatefulWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
const WebPage(this.title, this.url, {Key key}) : super(key: key);
|
||||
const WebPage(this.title, this.url, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<WebPage> createState() => _WebPageState();
|
||||
|
||||
0
lib/ui/components/components_constants.dart
Normal file
0
lib/ui/components/components_constants.dart
Normal file
286
lib/ui/components/dialog_widget.dart
Normal file
286
lib/ui/components/dialog_widget.dart
Normal file
@@ -0,0 +1,286 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/search/button_result.dart";
|
||||
import 'package:photos/models/typedefs.dart';
|
||||
import 'package:photos/theme/colors.dart';
|
||||
import 'package:photos/theme/effects.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/ui/components/text_input_widget.dart';
|
||||
import 'package:photos/utils/separators_util.dart';
|
||||
|
||||
///Will return null if dismissed by tapping outside
|
||||
Future<ButtonResult?> showDialogWidget({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
String? body,
|
||||
required List<ButtonWidget> buttons,
|
||||
IconData? icon,
|
||||
bool isDismissible = true,
|
||||
}) {
|
||||
return showDialog(
|
||||
barrierDismissible: isDismissible,
|
||||
barrierColor: backdropFaintDark,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final widthOfScreen = MediaQuery.of(context).size.width;
|
||||
final isMobileSmall = widthOfScreen <= mobileSmallThreshold;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: isMobileSmall ? 8 : 0),
|
||||
child: Dialog(
|
||||
insetPadding: EdgeInsets.zero,
|
||||
child: DialogWidget(
|
||||
title: title,
|
||||
body: body,
|
||||
buttons: buttons,
|
||||
icon: icon,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class DialogWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String? body;
|
||||
final List<ButtonWidget> buttons;
|
||||
final IconData? icon;
|
||||
const DialogWidget({
|
||||
required this.title,
|
||||
this.body,
|
||||
required this.buttons,
|
||||
this.icon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final widthOfScreen = MediaQuery.of(context).size.width;
|
||||
final isMobileSmall = widthOfScreen <= mobileSmallThreshold;
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Container(
|
||||
width: min(widthOfScreen, 320),
|
||||
padding: isMobileSmall
|
||||
? const EdgeInsets.all(0)
|
||||
: const EdgeInsets.fromLTRB(6, 8, 6, 6),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.backgroundElevated,
|
||||
boxShadow: shadowFloatLight,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ContentContainer(
|
||||
title: title,
|
||||
body: body,
|
||||
icon: icon,
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Actions(buttons),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContentContainer extends StatelessWidget {
|
||||
final String title;
|
||||
final String? body;
|
||||
final IconData? icon;
|
||||
const ContentContainer({
|
||||
required this.title,
|
||||
this.body,
|
||||
this.icon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
icon == null
|
||||
? const SizedBox.shrink()
|
||||
: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
icon == null ? const SizedBox.shrink() : const SizedBox(height: 19),
|
||||
Text(title, style: textTheme.largeBold),
|
||||
body != null ? const SizedBox(height: 19) : const SizedBox.shrink(),
|
||||
body != null
|
||||
? Text(
|
||||
body!,
|
||||
style: textTheme.body.copyWith(color: colorScheme.textMuted),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Actions extends StatelessWidget {
|
||||
final List<ButtonWidget> buttons;
|
||||
const Actions(this.buttons, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: addSeparators(
|
||||
buttons,
|
||||
const SizedBox(
|
||||
// In figma this white space is of height 8pts. But the Button
|
||||
// component has 1pts of invisible border by default in code. So two
|
||||
// 1pts borders will visually make the whitespace 8pts.
|
||||
// Height of button component in figma = 48, in code = 50 (2pts for
|
||||
// top + bottom border)
|
||||
height: 6,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextInputDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final String? body;
|
||||
final String submitButtonLabel;
|
||||
final IconData? icon;
|
||||
final String? label;
|
||||
final String? message;
|
||||
final FutureVoidCallbackParamStr onSubmit;
|
||||
final String? hintText;
|
||||
final IconData? prefixIcon;
|
||||
final String? initialValue;
|
||||
final Alignment? alignMessage;
|
||||
final int? maxLength;
|
||||
final bool showOnlyLoadingState;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool alwaysShowSuccessState;
|
||||
final bool isPasswordInput;
|
||||
const TextInputDialog({
|
||||
required this.title,
|
||||
this.body,
|
||||
required this.submitButtonLabel,
|
||||
required this.onSubmit,
|
||||
this.icon,
|
||||
this.label,
|
||||
this.message,
|
||||
this.hintText,
|
||||
this.prefixIcon,
|
||||
this.initialValue,
|
||||
this.alignMessage,
|
||||
this.maxLength,
|
||||
this.textCapitalization,
|
||||
this.showOnlyLoadingState = false,
|
||||
this.alwaysShowSuccessState = false,
|
||||
this.isPasswordInput = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TextInputDialog> createState() => _TextInputDialogState();
|
||||
}
|
||||
|
||||
class _TextInputDialogState extends State<TextInputDialog> {
|
||||
//the value of this ValueNotifier has no significance
|
||||
final _submitNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_submitNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final widthOfScreen = MediaQuery.of(context).size.width;
|
||||
final isMobileSmall = widthOfScreen <= mobileSmallThreshold;
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Container(
|
||||
width: min(widthOfScreen, 320),
|
||||
padding: isMobileSmall
|
||||
? const EdgeInsets.all(0)
|
||||
: const EdgeInsets.fromLTRB(6, 8, 6, 6),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.backgroundElevated,
|
||||
boxShadow: shadowFloatLight,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ContentContainer(
|
||||
title: widget.title,
|
||||
body: widget.body,
|
||||
icon: widget.icon,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 19),
|
||||
child: TextInputWidget(
|
||||
label: widget.label,
|
||||
message: widget.message,
|
||||
hintText: widget.hintText,
|
||||
prefixIcon: widget.prefixIcon,
|
||||
initialValue: widget.initialValue,
|
||||
alignMessage: widget.alignMessage,
|
||||
autoFocus: true,
|
||||
maxLength: widget.maxLength,
|
||||
submitNotifier: _submitNotifier,
|
||||
onSubmit: widget.onSubmit,
|
||||
popNavAfterSubmission: true,
|
||||
showOnlyLoadingState: widget.showOnlyLoadingState,
|
||||
textCapitalization: widget.textCapitalization,
|
||||
alwaysShowSuccessState: widget.alwaysShowSuccessState,
|
||||
isPasswordInput: widget.isPasswordInput,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.small,
|
||||
labelText: S.of(context).cancel,
|
||||
isInAlert: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonSize: ButtonSize.small,
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: widget.submitButtonLabel,
|
||||
onTap: () async {
|
||||
_submitNotifier.value = !_submitNotifier.value;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
382
lib/ui/components/text_input_widget.dart
Normal file
382
lib/ui/components/text_input_widget.dart
Normal file
@@ -0,0 +1,382 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:photos/models/execution_states.dart';
|
||||
import 'package:photos/models/typedefs.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/utils/debouncer.dart';
|
||||
import 'package:photos/utils/separators_util.dart';
|
||||
|
||||
class TextInputWidget extends StatefulWidget {
|
||||
final String? label;
|
||||
final String? message;
|
||||
final String? hintText;
|
||||
final IconData? prefixIcon;
|
||||
final String? initialValue;
|
||||
final Alignment? alignMessage;
|
||||
final bool? autoFocus;
|
||||
final int? maxLength;
|
||||
|
||||
///TextInputWidget will listen to this notifier and executes onSubmit when
|
||||
///notified.
|
||||
final ValueNotifier? submitNotifier;
|
||||
final bool alwaysShowSuccessState;
|
||||
final bool showOnlyLoadingState;
|
||||
final FutureVoidCallbackParamStr? onSubmit;
|
||||
final VoidCallbackParamStr? onChange;
|
||||
final bool popNavAfterSubmission;
|
||||
final bool shouldSurfaceExecutionStates;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool isPasswordInput;
|
||||
final bool cancellable;
|
||||
final bool shouldUnfocusOnCancelOrSubmit;
|
||||
const TextInputWidget({
|
||||
this.onSubmit,
|
||||
this.onChange,
|
||||
this.label,
|
||||
this.message,
|
||||
this.hintText,
|
||||
this.prefixIcon,
|
||||
this.initialValue,
|
||||
this.alignMessage,
|
||||
this.autoFocus,
|
||||
this.maxLength,
|
||||
this.submitNotifier,
|
||||
this.alwaysShowSuccessState = false,
|
||||
this.showOnlyLoadingState = false,
|
||||
this.popNavAfterSubmission = false,
|
||||
this.shouldSurfaceExecutionStates = true,
|
||||
this.textCapitalization = TextCapitalization.none,
|
||||
this.isPasswordInput = false,
|
||||
this.cancellable = false,
|
||||
this.shouldUnfocusOnCancelOrSubmit = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TextInputWidget> createState() => _TextInputWidgetState();
|
||||
}
|
||||
|
||||
class _TextInputWidgetState extends State<TextInputWidget> {
|
||||
ExecutionState executionState = ExecutionState.idle;
|
||||
final _textController = TextEditingController();
|
||||
final _debouncer = Debouncer(const Duration(milliseconds: 300));
|
||||
late final ValueNotifier<bool> _obscureTextNotifier;
|
||||
|
||||
///This is to pass if the TextInputWidget is in a dialog and an error is
|
||||
///thrown in executing onSubmit by passing it as arg in Navigator.pop()
|
||||
Exception? _exception;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
widget.submitNotifier?.addListener(_onSubmit);
|
||||
|
||||
if (widget.initialValue != null) {
|
||||
_textController.value = TextEditingValue(
|
||||
text: widget.initialValue!,
|
||||
selection: TextSelection.collapsed(offset: widget.initialValue!.length),
|
||||
);
|
||||
}
|
||||
if (widget.onChange != null) {
|
||||
_textController.addListener(() {
|
||||
widget.onChange!.call(_textController.text);
|
||||
});
|
||||
}
|
||||
_obscureTextNotifier = ValueNotifier(widget.isPasswordInput);
|
||||
_obscureTextNotifier.addListener(_safeRefresh);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.submitNotifier?.removeListener(_onSubmit);
|
||||
_obscureTextNotifier.dispose();
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (executionState == ExecutionState.successful) {
|
||||
Future.delayed(Duration(seconds: widget.popNavAfterSubmission ? 1 : 2),
|
||||
() {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
});
|
||||
}
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
var textInputChildren = <Widget>[];
|
||||
if (widget.label != null) {
|
||||
textInputChildren.add(Text(widget.label!));
|
||||
}
|
||||
textInputChildren.add(
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Material(
|
||||
child: TextFormField(
|
||||
textCapitalization: widget.textCapitalization!,
|
||||
autofocus: widget.autoFocus ?? false,
|
||||
controller: _textController,
|
||||
inputFormatters: widget.maxLength != null
|
||||
? [LengthLimitingTextInputFormatter(50)]
|
||||
: null,
|
||||
obscureText: _obscureTextNotifier.value,
|
||||
decoration: InputDecoration(
|
||||
hintText: widget.hintText,
|
||||
hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted),
|
||||
filled: true,
|
||||
fillColor: colorScheme.fillFaint,
|
||||
contentPadding: const EdgeInsets.fromLTRB(
|
||||
12,
|
||||
12,
|
||||
0,
|
||||
12,
|
||||
),
|
||||
border: const UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: colorScheme.strokeFaint),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 175),
|
||||
switchInCurve: Curves.easeInExpo,
|
||||
switchOutCurve: Curves.easeOutExpo,
|
||||
child: SuffixIconWidget(
|
||||
key: ValueKey(executionState),
|
||||
executionState: executionState,
|
||||
shouldSurfaceExecutionStates:
|
||||
widget.shouldSurfaceExecutionStates,
|
||||
obscureTextNotifier: _obscureTextNotifier,
|
||||
isPasswordInput: widget.isPasswordInput,
|
||||
textController: _textController,
|
||||
isCancellable: widget.cancellable,
|
||||
shouldUnfocusOnCancelOrSubmit:
|
||||
widget.shouldUnfocusOnCancelOrSubmit,
|
||||
),
|
||||
),
|
||||
),
|
||||
prefixIconConstraints: const BoxConstraints(
|
||||
maxHeight: 44,
|
||||
maxWidth: 44,
|
||||
minHeight: 44,
|
||||
minWidth: 44,
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints(
|
||||
maxHeight: 24,
|
||||
maxWidth: 48,
|
||||
minHeight: 24,
|
||||
minWidth: 48,
|
||||
),
|
||||
prefixIcon: widget.prefixIcon != null
|
||||
? Icon(
|
||||
widget.prefixIcon,
|
||||
color: colorScheme.strokeMuted,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onEditingComplete: () {
|
||||
_onSubmit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (widget.message != null) {
|
||||
textInputChildren.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Align(
|
||||
alignment: widget.alignMessage ?? Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.message!,
|
||||
style: textTheme.small.copyWith(color: colorScheme.textMuted),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
textInputChildren =
|
||||
addSeparators(textInputChildren, const SizedBox(height: 4));
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: textInputChildren,
|
||||
);
|
||||
}
|
||||
|
||||
void _safeRefresh() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
_debouncer.run(
|
||||
() => Future(() {
|
||||
setState(() {
|
||||
executionState = ExecutionState.inProgress;
|
||||
});
|
||||
}),
|
||||
);
|
||||
if (widget.shouldUnfocusOnCancelOrSubmit) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
try {
|
||||
await widget.onSubmit!.call(_textController.text);
|
||||
} catch (e) {
|
||||
executionState = ExecutionState.error;
|
||||
_debouncer.cancelDebounce();
|
||||
_exception = e as Exception;
|
||||
if (!widget.popNavAfterSubmission) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
widget.alwaysShowSuccessState && _debouncer.isActive()
|
||||
? executionState = ExecutionState.successful
|
||||
: null;
|
||||
_debouncer.cancelDebounce();
|
||||
if (executionState == ExecutionState.successful) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// when the time taken by widget.onSubmit is approximately equal to the debounce
|
||||
// time, the callback is getting executed when/after the if condition
|
||||
// below is executing/executed which results in execution state stuck at
|
||||
// idle state. This Future is for delaying the execution of the if
|
||||
// condition so that the calback in the debouncer finishes execution before.
|
||||
await Future.delayed(const Duration(milliseconds: 5));
|
||||
if (executionState == ExecutionState.inProgress ||
|
||||
executionState == ExecutionState.error) {
|
||||
if (executionState == ExecutionState.inProgress) {
|
||||
if (mounted) {
|
||||
if (widget.showOnlyLoadingState) {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
_popNavigatorStack(context);
|
||||
} else {
|
||||
setState(() {
|
||||
executionState = ExecutionState.successful;
|
||||
Future.delayed(
|
||||
Duration(
|
||||
seconds: widget.shouldSurfaceExecutionStates
|
||||
? (widget.popNavAfterSubmission ? 1 : 2)
|
||||
: 0,
|
||||
), () {
|
||||
widget.popNavAfterSubmission
|
||||
? _popNavigatorStack(context)
|
||||
: null;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (executionState == ExecutionState.error) {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
widget.popNavAfterSubmission
|
||||
? Future.delayed(
|
||||
const Duration(seconds: 0),
|
||||
() => _popNavigatorStack(context, e: _exception),
|
||||
)
|
||||
: null;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (widget.popNavAfterSubmission) {
|
||||
Future.delayed(
|
||||
Duration(seconds: widget.alwaysShowSuccessState ? 1 : 0),
|
||||
() => _popNavigatorStack(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _popNavigatorStack(BuildContext context, {Exception? e}) {
|
||||
Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null;
|
||||
}
|
||||
}
|
||||
|
||||
//todo: Add clear and custom icon for suffic icon
|
||||
class SuffixIconWidget extends StatelessWidget {
|
||||
final ExecutionState executionState;
|
||||
final bool shouldSurfaceExecutionStates;
|
||||
final TextEditingController textController;
|
||||
final ValueNotifier? obscureTextNotifier;
|
||||
final bool isPasswordInput;
|
||||
final bool isCancellable;
|
||||
final bool shouldUnfocusOnCancelOrSubmit;
|
||||
|
||||
const SuffixIconWidget({
|
||||
required this.executionState,
|
||||
required this.shouldSurfaceExecutionStates,
|
||||
required this.textController,
|
||||
this.obscureTextNotifier,
|
||||
this.isPasswordInput = false,
|
||||
this.isCancellable = false,
|
||||
this.shouldUnfocusOnCancelOrSubmit = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget trailingWidget;
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
if (executionState == ExecutionState.idle ||
|
||||
!shouldSurfaceExecutionStates) {
|
||||
if (isCancellable) {
|
||||
trailingWidget = GestureDetector(
|
||||
onTap: () {
|
||||
textController.clear();
|
||||
if (shouldUnfocusOnCancelOrSubmit) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
},
|
||||
child: Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: colorScheme.strokeMuted,
|
||||
),
|
||||
);
|
||||
} else if (isPasswordInput) {
|
||||
assert(obscureTextNotifier != null);
|
||||
trailingWidget = GestureDetector(
|
||||
onTap: () {
|
||||
obscureTextNotifier!.value = !obscureTextNotifier!.value;
|
||||
},
|
||||
child: Icon(
|
||||
obscureTextNotifier!.value
|
||||
? Icons.visibility_off_outlined
|
||||
: Icons.visibility,
|
||||
color: obscureTextNotifier!.value ? colorScheme.strokeMuted : null,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
trailingWidget = const SizedBox.shrink();
|
||||
}
|
||||
} else if (executionState == ExecutionState.inProgress) {
|
||||
trailingWidget = EnteLoadingWidget(
|
||||
color: colorScheme.strokeMuted,
|
||||
);
|
||||
} else if (executionState == ExecutionState.successful) {
|
||||
trailingWidget = Icon(
|
||||
Icons.check_outlined,
|
||||
size: 22,
|
||||
color: colorScheme.primary500,
|
||||
);
|
||||
} else {
|
||||
trailingWidget = const SizedBox.shrink();
|
||||
}
|
||||
return trailingWidget;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// @dart=2.9
|
||||
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
@@ -95,12 +93,13 @@ class AboutSectionWidget extends StatelessWidget {
|
||||
class AboutMenuItemWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
final String webPageTitle;
|
||||
final String? webPageTitle;
|
||||
|
||||
const AboutMenuItemWidget({
|
||||
@required this.title,
|
||||
@required this.url,
|
||||
required this.title,
|
||||
required this.url,
|
||||
this.webPageTitle,
|
||||
Key key,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user