@@ -5,8 +5,7 @@ import 'package:subctrl/domain/entities/tag.dart';
55import 'package:subctrl/presentation/formatters/date_formatter.dart' ;
66import 'package:subctrl/presentation/l10n/app_localizations.dart' ;
77import 'package:subctrl/presentation/mappers/billing_cycle_labels.dart' ;
8- import 'package:subctrl/presentation/widgets/currency_picker.dart' ;
9- import 'package:subctrl/presentation/widgets/tag_picker.dart' ;
8+ import 'package:subctrl/presentation/utils/color_utils.dart' ;
109
1110class AddSubscriptionSheet extends StatefulWidget {
1211 const AddSubscriptionSheet ({
@@ -120,42 +119,99 @@ class _AddSubscriptionSheetState extends State<AddSubscriptionSheet> {
120119 }
121120
122121 Future <void > _pickCurrency (FormFieldState <String > state) async {
123- final selected = await showCurrencyPicker (
122+ if (widget.currencies.isEmpty) return ;
123+ final localizations = AppLocalizations .of (context);
124+ var tempIndex = widget.currencies.indexWhere (
125+ (currency) => currency.code.toUpperCase () == _currencyCode.toUpperCase (),
126+ );
127+ if (tempIndex < 0 ) tempIndex = 0 ;
128+ final controller = FixedExtentScrollController (initialItem: tempIndex);
129+
130+ await showCupertinoModalPopup <void >(
124131 context: context,
125- currencies: widget.currencies,
126- selectedCode: _currencyCode,
132+ builder: (context) {
133+ return CupertinoActionSheet (
134+ title: Text (localizations.currencyLabel),
135+ message: SizedBox (
136+ height: 200 ,
137+ child: CupertinoPicker (
138+ itemExtent: 32 ,
139+ scrollController: controller,
140+ onSelectedItemChanged: (index) => tempIndex = index,
141+ children: widget.currencies
142+ .map (
143+ (currency) => Center (
144+ child: Row (
145+ mainAxisSize: MainAxisSize .min,
146+ children: [
147+ if ((currency.symbol ?? '' ).trim ().isNotEmpty)
148+ Padding (
149+ padding: const EdgeInsets .only (right: 8 ),
150+ child: Text (currency.symbol! .trim ()),
151+ ),
152+ Text (currency.code.toUpperCase ()),
153+ ],
154+ ),
155+ ),
156+ )
157+ .toList (growable: false ),
158+ ),
159+ ),
160+ cancelButton: CupertinoActionSheetAction (
161+ onPressed: () => Navigator .of (context).pop (),
162+ child: Text (localizations.done),
163+ ),
164+ );
165+ },
127166 );
128- if (selected != null ) {
129- setState (() => _currencyCode = selected.toUpperCase ());
130- state.didChange (_currencyCode);
131- }
167+
168+ if (! mounted) return ;
169+ final selected = widget.currencies[tempIndex];
170+ setState (() => _currencyCode = selected.code.toUpperCase ());
171+ state.didChange (_currencyCode);
132172 }
133173
134174 Future <void > _pickCycle (FormFieldState <BillingCycle > state) async {
135175 final localizations = AppLocalizations .of (context);
136- final cycle = await showCupertinoModalPopup <BillingCycle >(
176+ final initialIndex = _orderedCycles.indexOf (_cycle);
177+ var tempIndex = initialIndex < 0 ? 0 : initialIndex;
178+ final controller = FixedExtentScrollController (initialItem: tempIndex);
179+ await showCupertinoModalPopup <void >(
137180 context: context,
138181 builder: (context) {
139182 return CupertinoActionSheet (
140183 title: Text (localizations.periodLabel),
141- actions: [
142- for (final option in _orderedCycles)
143- CupertinoActionSheetAction (
144- onPressed: () => Navigator .of (context).pop (option),
145- child: Text (billingCycleLongLabel (option, localizations)),
146- ),
147- ],
184+ message: SizedBox (
185+ height: 200 ,
186+ child: CupertinoPicker (
187+ itemExtent: 40 ,
188+ scrollController: controller,
189+ onSelectedItemChanged: (index) => tempIndex = index,
190+ children: _orderedCycles
191+ .map (
192+ (option) => Center (
193+ child: Text (
194+ billingCycleLongLabel (option, localizations),
195+ style: CupertinoTheme .of (
196+ context,
197+ ).textTheme.textStyle.copyWith (fontSize: 19 ),
198+ ),
199+ ),
200+ )
201+ .toList (growable: false ),
202+ ),
203+ ),
148204 cancelButton: CupertinoActionSheetAction (
149205 onPressed: () => Navigator .of (context).pop (),
150- child: Text (localizations.settingsClose ),
206+ child: Text (localizations.done ),
151207 ),
152208 );
153209 },
154210 );
155- if (cycle != null ) {
156- setState (() => _cycle = cycle) ;
157- state. didChange (cycle );
158- }
211+ if (! mounted) return ;
212+ final selected = _orderedCycles[tempIndex] ;
213+ setState (() => _cycle = selected );
214+ state. didChange (selected);
159215 }
160216
161217 Future <void > _pickPurchaseDate (FormFieldState <DateTime ?> state) async {
@@ -164,37 +220,21 @@ class _AddSubscriptionSheetState extends State<AddSubscriptionSheet> {
164220 context: context,
165221 builder: (context) {
166222 final localizations = AppLocalizations .of (context);
167- final background = CupertinoColors .systemBackground.resolveFrom (
168- context,
169- );
170- return Container (
171- color: background,
172- height: 320 ,
173- child: Column (
174- children: [
175- SizedBox (
176- height: 44 ,
177- child: Row (
178- mainAxisAlignment: MainAxisAlignment .end,
179- children: [
180- CupertinoButton (
181- padding: const EdgeInsets .symmetric (horizontal: 16 ),
182- onPressed: () => Navigator .of (context).pop (),
183- child: Text (localizations.done),
184- ),
185- ],
186- ),
187- ),
188- Expanded (
189- child: CupertinoDatePicker (
190- mode: CupertinoDatePickerMode .date,
191- initialDateTime: tempDate,
192- minimumDate: DateTime (DateTime .now ().year - 10 ),
193- maximumDate: DateTime (DateTime .now ().year + 5 ),
194- onDateTimeChanged: (value) => tempDate = value,
195- ),
196- ),
197- ],
223+ return CupertinoActionSheet (
224+ title: Text (localizations.purchaseDateLabel),
225+ message: SizedBox (
226+ height: 200 ,
227+ child: CupertinoDatePicker (
228+ mode: CupertinoDatePickerMode .date,
229+ initialDateTime: tempDate,
230+ minimumDate: DateTime (DateTime .now ().year - 10 ),
231+ maximumDate: DateTime (DateTime .now ().year + 5 ),
232+ onDateTimeChanged: (value) => tempDate = value,
233+ ),
234+ ),
235+ cancelButton: CupertinoActionSheetAction (
236+ onPressed: () => Navigator .of (context).pop (),
237+ child: Text (localizations.done),
198238 ),
199239 );
200240 },
@@ -206,20 +246,73 @@ class _AddSubscriptionSheetState extends State<AddSubscriptionSheet> {
206246
207247 Future <void > _pickTag (FormFieldState <int ?> state) async {
208248 if (widget.tags.isEmpty) return ;
209- final result = await showTagPicker (
249+ final localizations = AppLocalizations .of (context);
250+ final options = [
251+ _TagOption .none (localizations.subscriptionTagNone),
252+ ...widget.tags.map ((tag) => _TagOption .tag (tag)),
253+ ];
254+ var initialIndex = options.indexWhere (
255+ (option) => option.matches (_selectedTagId),
256+ );
257+ if (initialIndex < 0 ) initialIndex = 0 ;
258+ var tempIndex = initialIndex;
259+ final controller = FixedExtentScrollController (initialItem: tempIndex);
260+
261+ await showCupertinoModalPopup <void >(
210262 context: context,
211- tags: widget.tags,
212- selectedTagId: _selectedTagId,
263+ builder: (context) {
264+ return CupertinoActionSheet (
265+ title: Text (localizations.subscriptionTagLabel),
266+ message: SizedBox (
267+ height: 200 ,
268+ child: CupertinoPicker (
269+ itemExtent: 40 ,
270+ scrollController: controller,
271+ onSelectedItemChanged: (index) => tempIndex = index,
272+ children: options
273+ .map (
274+ (option) => Center (
275+ child: Row (
276+ mainAxisSize: MainAxisSize .min,
277+ children: [
278+ if (option.colorHex != null )
279+ Container (
280+ width: 12 ,
281+ height: 12 ,
282+ decoration: BoxDecoration (
283+ shape: BoxShape .circle,
284+ color: colorFromHex (
285+ option.colorHex! ,
286+ fallbackColor: const Color (0xFF000000 ),
287+ ),
288+ ),
289+ ),
290+ if (option.colorHex != null ) const SizedBox (width: 8 ),
291+ Text (
292+ option.label,
293+ style: CupertinoTheme .of (
294+ context,
295+ ).textTheme.textStyle.copyWith (fontSize: 19 ),
296+ ),
297+ ],
298+ ),
299+ ),
300+ )
301+ .toList (growable: false ),
302+ ),
303+ ),
304+ cancelButton: CupertinoActionSheetAction (
305+ onPressed: () => Navigator .of (context).pop (),
306+ child: Text (localizations.done),
307+ ),
308+ );
309+ },
213310 );
214- if (result == null ) return ;
311+
215312 if (! mounted) return ;
216- if (result == - 1 ) {
217- setState (() => _selectedTagId = null );
218- state.didChange (null );
219- } else {
220- setState (() => _selectedTagId = result);
221- state.didChange (result);
222- }
313+ final selected = options[tempIndex];
314+ setState (() => _selectedTagId = selected.tagId);
315+ state.didChange (selected.tagId);
223316 }
224317
225318 void _handleSubmit () {
@@ -657,3 +750,18 @@ class _AddSubscriptionSheetState extends State<AddSubscriptionSheet> {
657750 );
658751 }
659752}
753+
754+ class _TagOption {
755+ const _TagOption ._(this .tagId, this .label, this .colorHex);
756+
757+ factory _TagOption .none (String label) => _TagOption ._(null , label, null );
758+
759+ factory _TagOption .tag (Tag tag) =>
760+ _TagOption ._(tag.id, tag.name, tag.colorHex);
761+
762+ final int ? tagId;
763+ final String label;
764+ final String ? colorHex;
765+
766+ bool matches (int ? selectedTagId) => tagId == selectedTagId;
767+ }
0 commit comments