dev
dart 125 lines 4.39 KB
Raw
1 import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
2 import 'package:flutter/material.dart';
3
4 void nothing() {}
5
6 class ModalTopBar extends StatelessWidget {
7 ModalTopBar(
8 {super.key,
9 required this.title,
10 this.subtitle,
11 this.onLeadingPressed = nothing,
12 this.onTrailingPressed = nothing,
13 this.leadingIcon,
14 this.trailingIcon,
15 this.padding,
16 this.leadingWidget,
17 this.trailingWidget,
18 this.leadingSemanticLabel,
19 this.trailingSemanticLabel}) {
20 if (leadingIcon != null && leadingWidget != null) {
21 throw Exception("Cannot have both leadingIcon and leadingWidget");
22 }
23 if (trailingIcon != null && trailingWidget != null) {
24 throw Exception("Cannot have both trailingIcon and trailingWidget");
25 }
26 }
27
28 final String title;
29 final String? subtitle;
30 final VoidCallback onLeadingPressed;
31 final VoidCallback onTrailingPressed;
32 final Widget? leadingIcon;
33 final EdgeInsets? padding;
34 final Widget? trailingIcon;
35 final Widget? leadingWidget;
36 final Widget? trailingWidget;
37
38 /// Accessible name for the leading chrome button. Required (and must be
39 /// non-empty) whenever a [leadingIcon] is supplied, because the icon alone
40 /// does not say whether it closes the modal or goes back. Must be localized
41 /// by the caller.
42 final String? leadingSemanticLabel;
43
44 /// Accessible name for the trailing chrome button. Required (and must be
45 /// non-empty) whenever a [trailingIcon] is supplied. Must be localized by
46 /// the caller.
47 final String? trailingSemanticLabel;
48
49 static const buttonSize = 36.0;
50
51 @override
52 Widget build(BuildContext context) {
53 return Padding(
54 padding: padding ?? EdgeInsets.all(18),
55 child: Stack(
56 alignment: Alignment.topCenter,
57 children: [
58 Positioned(
59 top: 6,
60 child: Row(
61 mainAxisSize: MainAxisSize.max,
62 mainAxisAlignment: MainAxisAlignment.center,
63 spacing: 4,
64 children: [
65 AnimatedSwitcher(
66 duration: const Duration(milliseconds: 200),
67 child: Semantics(
68 key: ValueKey(title),
69 header: title.isNotEmpty,
70 // Android reads the heading from headingLevel since the
71 // Flutter 3.41 engine; header: alone only covers iOS.
72 headingLevel: title.isNotEmpty ? 1 : null,
73 child: Text(
74 title,
75 style: Theme.of(context).textTheme.headlineMedium,
76 ),
77 ),
78 ),
79 if (subtitle != null && subtitle!.isNotEmpty)
80 Text(subtitle!,
81 style: Theme.of(context)
82 .textTheme
83 .headlineMedium
84 ?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant))
85 ],
86 ),
87 ),
88 Row(
89 mainAxisSize: MainAxisSize.max,
90 mainAxisAlignment: MainAxisAlignment.spaceBetween,
91 children: [
92 if (leadingIcon != null || leadingWidget != null)
93 leadingIcon != null
94 ? ModernButton(
95 key: ValueKey(leadingIcon.hashCode),
96 size: buttonSize,
97 onPressed: onLeadingPressed,
98 icon: leadingIcon!,
99 semanticLabel: leadingSemanticLabel,
100 iconColor: Theme.of(context).colorScheme.onSurfaceVariant)
101 : leadingWidget!
102 else
103 Container(width: buttonSize),
104 if (trailingIcon != null || trailingWidget != null)
105 AnimatedSwitcher(
106 duration: const Duration(milliseconds: 200),
107 child: trailingIcon != null
108 ? ModernButton(
109 key: ValueKey(trailingIcon.hashCode),
110 size: buttonSize,
111 onPressed: onTrailingPressed,
112 icon: trailingIcon!,
113 semanticLabel: trailingSemanticLabel,
114 )
115 : trailingWidget!,
116 )
117 else
118 Container(width: buttonSize),
119 ],
120 ),
121 ],
122 ),
123 );
124 }
125 }