feat(ui): redesign feedback dialogs with pill style for legacy mode
This commit is contained in:
@@ -135,7 +135,7 @@ class _RequestCancelSection extends ConsumerWidget {
|
|||||||
await showInfoDialog(
|
await showInfoDialog(
|
||||||
context,
|
context,
|
||||||
I18n.accountDeletionHasDevices,
|
I18n.accountDeletionHasDevices,
|
||||||
autoDismiss: null,
|
autoDismiss: const Duration(seconds: 5),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ class SfColors extends ThemeExtension<SfColors> {
|
|||||||
required this.cardColorSet1,
|
required this.cardColorSet1,
|
||||||
required this.cardColorSet2,
|
required this.cardColorSet2,
|
||||||
required this.disabledCardColors,
|
required this.disabledCardColors,
|
||||||
|
required this.successBackground,
|
||||||
|
required this.successCircle,
|
||||||
|
required this.successText,
|
||||||
|
required this.errorBackground,
|
||||||
|
required this.errorCircle,
|
||||||
|
required this.errorText,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Brand secondary color. Also mirrored in [ColorScheme.tertiary].
|
/// Brand secondary color. Also mirrored in [ColorScheme.tertiary].
|
||||||
@@ -34,18 +40,38 @@ class SfColors extends ThemeExtension<SfColors> {
|
|||||||
/// Gradient applied to disabled cards.
|
/// Gradient applied to disabled cards.
|
||||||
final List<Color> disabledCardColors;
|
final List<Color> disabledCardColors;
|
||||||
|
|
||||||
|
/// Feedback dialog colors.
|
||||||
|
final Color successBackground;
|
||||||
|
final Color successCircle;
|
||||||
|
final Color successText;
|
||||||
|
final Color errorBackground;
|
||||||
|
final Color errorCircle;
|
||||||
|
final Color errorText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SfColors copyWith({
|
SfColors copyWith({
|
||||||
Color? legacyPrimary,
|
Color? legacyPrimary,
|
||||||
List<Color>? cardColorSet1,
|
List<Color>? cardColorSet1,
|
||||||
List<Color>? cardColorSet2,
|
List<Color>? cardColorSet2,
|
||||||
List<Color>? disabledCardColors,
|
List<Color>? disabledCardColors,
|
||||||
|
Color? successBackground,
|
||||||
|
Color? successCircle,
|
||||||
|
Color? successText,
|
||||||
|
Color? errorBackground,
|
||||||
|
Color? errorCircle,
|
||||||
|
Color? errorText,
|
||||||
}) {
|
}) {
|
||||||
return SfColors(
|
return SfColors(
|
||||||
legacyPrimary: legacyPrimary ?? this.legacyPrimary,
|
legacyPrimary: legacyPrimary ?? this.legacyPrimary,
|
||||||
cardColorSet1: cardColorSet1 ?? this.cardColorSet1,
|
cardColorSet1: cardColorSet1 ?? this.cardColorSet1,
|
||||||
cardColorSet2: cardColorSet2 ?? this.cardColorSet2,
|
cardColorSet2: cardColorSet2 ?? this.cardColorSet2,
|
||||||
disabledCardColors: disabledCardColors ?? this.disabledCardColors,
|
disabledCardColors: disabledCardColors ?? this.disabledCardColors,
|
||||||
|
successBackground: successBackground ?? this.successBackground,
|
||||||
|
successCircle: successCircle ?? this.successCircle,
|
||||||
|
successText: successText ?? this.successText,
|
||||||
|
errorBackground: errorBackground ?? this.errorBackground,
|
||||||
|
errorCircle: errorCircle ?? this.errorCircle,
|
||||||
|
errorText: errorText ?? this.errorText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +83,12 @@ class SfColors extends ThemeExtension<SfColors> {
|
|||||||
cardColorSet1: _lerpList(cardColorSet1, other.cardColorSet1, t),
|
cardColorSet1: _lerpList(cardColorSet1, other.cardColorSet1, t),
|
||||||
cardColorSet2: _lerpList(cardColorSet2, other.cardColorSet2, t),
|
cardColorSet2: _lerpList(cardColorSet2, other.cardColorSet2, t),
|
||||||
disabledCardColors: _lerpList(disabledCardColors, other.disabledCardColors, t),
|
disabledCardColors: _lerpList(disabledCardColors, other.disabledCardColors, t),
|
||||||
|
successBackground: Color.lerp(successBackground, other.successBackground, t)!,
|
||||||
|
successCircle: Color.lerp(successCircle, other.successCircle, t)!,
|
||||||
|
successText: Color.lerp(successText, other.successText, t)!,
|
||||||
|
errorBackground: Color.lerp(errorBackground, other.errorBackground, t)!,
|
||||||
|
errorCircle: Color.lerp(errorCircle, other.errorCircle, t)!,
|
||||||
|
errorText: Color.lerp(errorText, other.errorText, t)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +110,12 @@ class SfColors extends ThemeExtension<SfColors> {
|
|||||||
Color(0xFF797676),
|
Color(0xFF797676),
|
||||||
Color(0xFF5F5A5A),
|
Color(0xFF5F5A5A),
|
||||||
],
|
],
|
||||||
|
successBackground: Color(0xFFE8F5EC),
|
||||||
|
successCircle: Color(0xFF66D693),
|
||||||
|
successText: Color(0xFF424242),
|
||||||
|
errorBackground: Color(0xFFFDE8E8),
|
||||||
|
errorCircle: Color(0xFFE57373),
|
||||||
|
errorText: Color(0xFF424242),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Dark variant — cards kept visually consistent; brand tone lightened.
|
/// Dark variant — cards kept visually consistent; brand tone lightened.
|
||||||
@@ -90,6 +128,12 @@ class SfColors extends ThemeExtension<SfColors> {
|
|||||||
Color(0xFF494949),
|
Color(0xFF494949),
|
||||||
Color(0xFF363636),
|
Color(0xFF363636),
|
||||||
],
|
],
|
||||||
|
successBackground: Color(0xFF1B3A2A),
|
||||||
|
successCircle: Color(0xFF66D693),
|
||||||
|
successText: Color(0xFFE0E0E0),
|
||||||
|
errorBackground: Color(0xFF3A1B1B),
|
||||||
|
errorCircle: Color(0xFFE57373),
|
||||||
|
errorText: Color(0xFFE0E0E0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ Future<void> showErrorDialog(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
String messageKey, {
|
String messageKey, {
|
||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showFeedbackDialog(
|
return _showPillFeedback(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
kind: _FeedbackKind.error,
|
kind: _FeedbackKind.error,
|
||||||
autoDismiss: null,
|
autoDismiss: autoDismiss,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,9 +24,9 @@ Future<void> showSuccessDialog(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
String messageKey, {
|
String messageKey, {
|
||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
Duration autoDismiss = const Duration(seconds: 2),
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showFeedbackDialog(
|
return _showPillFeedback(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
@@ -38,9 +39,9 @@ Future<void> showInfoDialog(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
String messageKey, {
|
String messageKey, {
|
||||||
Map<String, dynamic>? args,
|
Map<String, dynamic>? args,
|
||||||
Duration? autoDismiss = const Duration(seconds: 2),
|
Duration autoDismiss = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
return _showFeedbackDialog(
|
return _showPillFeedback(
|
||||||
context,
|
context,
|
||||||
messageKey: messageKey,
|
messageKey: messageKey,
|
||||||
args: args,
|
args: args,
|
||||||
@@ -49,45 +50,162 @@ Future<void> showInfoDialog(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showFeedbackDialog(
|
Future<void> _showPillFeedback(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String messageKey,
|
required String messageKey,
|
||||||
required Map<String, dynamic>? args,
|
required Map<String, dynamic>? args,
|
||||||
required _FeedbackKind kind,
|
required _FeedbackKind kind,
|
||||||
required Duration? autoDismiss,
|
required Duration autoDismiss,
|
||||||
}) async {
|
}) async {
|
||||||
Timer? timer;
|
final overlay = Overlay.of(context);
|
||||||
await showDialog<void>(
|
final resolved = context.translate(messageKey, args: args);
|
||||||
context: context,
|
|
||||||
barrierDismissible: autoDismiss != null,
|
|
||||||
builder: (ctx) {
|
|
||||||
if (autoDismiss != null) {
|
|
||||||
timer = Timer(autoDismiss, () {
|
|
||||||
if (Navigator.of(ctx).canPop()) {
|
|
||||||
Navigator.of(ctx).pop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final resolved = ctx.translate(messageKey, args: args);
|
final (backgroundColor, circleColor, iconData) = switch (kind) {
|
||||||
final theme = Theme.of(ctx);
|
_FeedbackKind.success => (
|
||||||
final (icon, color) = switch (kind) {
|
const Color(0xFFE8F5EC),
|
||||||
_FeedbackKind.error => (Icons.error_outline, theme.colorScheme.error),
|
const Color(0xFF66D693),
|
||||||
_FeedbackKind.success => (Icons.check_circle_outline, Colors.green),
|
Icons.check,
|
||||||
_FeedbackKind.info => (Icons.info_outline, theme.colorScheme.primary),
|
),
|
||||||
};
|
_FeedbackKind.error => (
|
||||||
|
const Color(0xFFFDE8E8),
|
||||||
|
const Color(0xFFE57373),
|
||||||
|
Icons.close,
|
||||||
|
),
|
||||||
|
_FeedbackKind.info => (
|
||||||
|
const Color(0xFFE3F2FD),
|
||||||
|
const Color(0xFF64B5F6),
|
||||||
|
Icons.info_outline,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
return AlertDialog(
|
late final OverlayEntry entry;
|
||||||
icon: Icon(icon, color: color, size: 48),
|
late final AnimationController controller;
|
||||||
content: Text(resolved, textAlign: TextAlign.center),
|
|
||||||
actions: [
|
controller = AnimationController(
|
||||||
TextButton(
|
duration: const Duration(milliseconds: 350),
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
vsync: overlay,
|
||||||
child: Text(ctx.translate(I18n.accept)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
timer?.cancel();
|
|
||||||
|
final curvedAnimation = CurvedAnimation(
|
||||||
|
parent: controller,
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
reverseCurve: Curves.easeInCubic,
|
||||||
|
);
|
||||||
|
|
||||||
|
void dismiss() {
|
||||||
|
controller.reverse().then((_) {
|
||||||
|
entry.remove();
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = OverlayEntry(
|
||||||
|
builder: (_) => Positioned.fill(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: dismiss,
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Center(
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: Tween<double>(begin: 0.8, end: 1.0).animate(curvedAnimation),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: curvedAnimation,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 58,
|
||||||
|
top: 14,
|
||||||
|
bottom: 14,
|
||||||
|
right: 16,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.white,
|
||||||
|
blurRadius: 12.85,
|
||||||
|
spreadRadius: 8.91,
|
||||||
|
offset: Offset(0, 4.11),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
resolved,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 30,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 46,
|
||||||
|
height: 46,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: circleColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
iconData,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 38,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: -8,
|
||||||
|
right: 18,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: dismiss,
|
||||||
|
child: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.08),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
overlay.insert(entry);
|
||||||
|
controller.forward();
|
||||||
|
|
||||||
|
await Future.delayed(autoDismiss);
|
||||||
|
if (controller.isAnimating ||
|
||||||
|
controller.status == AnimationStatus.completed) {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user