kasy-cli 1.18.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kasy.js +3 -1
- package/lib/commands/new.js +99 -105
- package/lib/commands/run.js +34 -6
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +79 -0
- package/lib/utils/brand.js +1 -1
- package/lib/utils/i18n/messages-en.js +6 -0
- package/lib/utils/i18n/messages-es.js +6 -0
- package/lib/utils/i18n/messages-pt.js +6 -0
- package/package.json +1 -2
- package/templates/firebase/lib/components/kasy_date_picker.dart +1670 -331
- package/templates/firebase/lib/components/kasy_tabs.dart +111 -72
- package/templates/firebase/lib/components/kasy_text_area.dart +9 -4
- package/templates/firebase/lib/components/kasy_text_field.dart +96 -36
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -2
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +88 -35
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +7 -43
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +118 -16
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +14 -20
- package/templates/firebase/lib/core/security/secured_storage.dart +56 -15
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +18 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -6
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +6 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -2
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +457 -73
- package/templates/firebase/lib/features/home/home_page.dart +17 -40
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -16
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +0 -4
- package/templates/firebase/lib/main.dart +34 -34
- package/templates/firebase/pubspec.yaml +1 -0
- package/templates/firebase/storage.cors.json +8 -0
- package/templates/firebase/web/index.html +15 -2
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +0 -22
|
@@ -236,20 +236,28 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
|
|
|
236
236
|
title: 'DatePicker',
|
|
237
237
|
variants: [
|
|
238
238
|
ComponentPreviewVariant(
|
|
239
|
-
label: '
|
|
240
|
-
builder:
|
|
239
|
+
label: 'Presentations',
|
|
240
|
+
builder: _buildDatePickerPresentations,
|
|
241
241
|
),
|
|
242
242
|
ComponentPreviewVariant(
|
|
243
|
-
label: '
|
|
244
|
-
builder:
|
|
243
|
+
label: 'Range selection',
|
|
244
|
+
builder: _buildDatePickerRange,
|
|
245
245
|
),
|
|
246
246
|
ComponentPreviewVariant(
|
|
247
|
-
label: '
|
|
248
|
-
builder:
|
|
247
|
+
label: 'Multi-month',
|
|
248
|
+
builder: _buildDatePickerMultiMonth,
|
|
249
249
|
),
|
|
250
250
|
ComponentPreviewVariant(
|
|
251
|
-
label: '
|
|
252
|
-
builder:
|
|
251
|
+
label: 'Date formats',
|
|
252
|
+
builder: _buildDatePickerFormats,
|
|
253
|
+
),
|
|
254
|
+
ComponentPreviewVariant(
|
|
255
|
+
label: 'Variants',
|
|
256
|
+
builder: _buildDatePickerVariants,
|
|
257
|
+
),
|
|
258
|
+
ComponentPreviewVariant(
|
|
259
|
+
label: 'Field states',
|
|
260
|
+
builder: _buildDatePickerFieldStates,
|
|
253
261
|
),
|
|
254
262
|
],
|
|
255
263
|
);
|
|
@@ -677,71 +685,6 @@ class _SidebarPreviewState extends State<_SidebarPreview> {
|
|
|
677
685
|
|
|
678
686
|
|
|
679
687
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
680
|
-
// DatePicker — interactive demos
|
|
681
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
682
|
-
|
|
683
|
-
Widget _buildDatePickerBasic(BuildContext context) =>
|
|
684
|
-
const _DatePickerPreview(variant: _DatePickerVariant.basic);
|
|
685
|
-
|
|
686
|
-
Widget _buildDatePickerWithLabel(BuildContext context) =>
|
|
687
|
-
const _DatePickerPreview(variant: _DatePickerVariant.withLabel);
|
|
688
|
-
|
|
689
|
-
Widget _buildDatePickerMinMax(BuildContext context) =>
|
|
690
|
-
const _DatePickerPreview(variant: _DatePickerVariant.minMax);
|
|
691
|
-
|
|
692
|
-
Widget _buildDatePickerDisabled(BuildContext context) =>
|
|
693
|
-
const _DatePickerPreview(variant: _DatePickerVariant.disabled);
|
|
694
|
-
|
|
695
|
-
enum _DatePickerVariant { basic, withLabel, minMax, disabled }
|
|
696
|
-
|
|
697
|
-
class _DatePickerPreview extends StatefulWidget {
|
|
698
|
-
const _DatePickerPreview({required this.variant});
|
|
699
|
-
|
|
700
|
-
final _DatePickerVariant variant;
|
|
701
|
-
|
|
702
|
-
@override
|
|
703
|
-
State<_DatePickerPreview> createState() => _DatePickerPreviewState();
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
class _DatePickerPreviewState extends State<_DatePickerPreview> {
|
|
707
|
-
DateTime? _date;
|
|
708
|
-
|
|
709
|
-
@override
|
|
710
|
-
Widget build(BuildContext context) {
|
|
711
|
-
final DateTime today = DateTime.now();
|
|
712
|
-
final DateTime minDate = today.subtract(const Duration(days: 30));
|
|
713
|
-
final DateTime maxDate = today.add(const Duration(days: 60));
|
|
714
|
-
|
|
715
|
-
return Padding(
|
|
716
|
-
padding: const EdgeInsets.all(KasySpacing.md),
|
|
717
|
-
child: switch (widget.variant) {
|
|
718
|
-
_DatePickerVariant.basic => KasyDatePicker(
|
|
719
|
-
value: _date,
|
|
720
|
-
onChanged: (d) => setState(() => _date = d),
|
|
721
|
-
),
|
|
722
|
-
_DatePickerVariant.withLabel => KasyDatePicker(
|
|
723
|
-
label: 'Date of birth',
|
|
724
|
-
value: _date,
|
|
725
|
-
onChanged: (d) => setState(() => _date = d),
|
|
726
|
-
),
|
|
727
|
-
_DatePickerVariant.minMax => KasyDatePicker(
|
|
728
|
-
label: 'Appointment date',
|
|
729
|
-
value: _date,
|
|
730
|
-
minDate: minDate,
|
|
731
|
-
maxDate: maxDate,
|
|
732
|
-
onChanged: (d) => setState(() => _date = d),
|
|
733
|
-
),
|
|
734
|
-
_DatePickerVariant.disabled => KasyDatePicker(
|
|
735
|
-
label: 'Locked date',
|
|
736
|
-
value: today,
|
|
737
|
-
enabled: false,
|
|
738
|
-
onChanged: (d) => setState(() => _date = d),
|
|
739
|
-
),
|
|
740
|
-
},
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
688
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
746
689
|
// Tabs — interactive demos
|
|
747
690
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -8607,3 +8550,444 @@ class _SwipePreviewTile extends StatelessWidget {
|
|
|
8607
8550
|
);
|
|
8608
8551
|
}
|
|
8609
8552
|
}
|
|
8553
|
+
|
|
8554
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8555
|
+
// DatePicker — Presentations
|
|
8556
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8557
|
+
|
|
8558
|
+
/// Maps the running app's locale onto a [KasyDatePickerLocale] preset so the
|
|
8559
|
+
/// preview demonstrates the same regional defaults (month names, week start,
|
|
8560
|
+
/// date format) the user would see in their own app.
|
|
8561
|
+
KasyDatePickerLocale _datePickerLocaleForApp(BuildContext context) {
|
|
8562
|
+
switch (Localizations.localeOf(context).languageCode) {
|
|
8563
|
+
case 'es':
|
|
8564
|
+
return KasyDatePickerLocale.es;
|
|
8565
|
+
case 'pt':
|
|
8566
|
+
return KasyDatePickerLocale.pt;
|
|
8567
|
+
default:
|
|
8568
|
+
return KasyDatePickerLocale.en;
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
Widget _buildDatePickerPresentations(BuildContext context) =>
|
|
8573
|
+
const _DatePickerPresentationsPreview();
|
|
8574
|
+
|
|
8575
|
+
class _DatePickerPresentationsPreview extends StatefulWidget {
|
|
8576
|
+
const _DatePickerPresentationsPreview();
|
|
8577
|
+
|
|
8578
|
+
@override
|
|
8579
|
+
State<_DatePickerPresentationsPreview> createState() =>
|
|
8580
|
+
_DatePickerPresentationsPreviewState();
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8583
|
+
class _DatePickerPresentationsPreviewState
|
|
8584
|
+
extends State<_DatePickerPresentationsPreview> {
|
|
8585
|
+
DateTime? _popoverDate;
|
|
8586
|
+
DateTime? _dialogDate;
|
|
8587
|
+
DateTime? _sheetDate;
|
|
8588
|
+
|
|
8589
|
+
@override
|
|
8590
|
+
Widget build(BuildContext context) {
|
|
8591
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8592
|
+
return Column(
|
|
8593
|
+
mainAxisSize: MainAxisSize.min,
|
|
8594
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8595
|
+
children: [
|
|
8596
|
+
KasyDatePicker(
|
|
8597
|
+
label: 'Popover',
|
|
8598
|
+
placeholder: 'Choose a date',
|
|
8599
|
+
value: _popoverDate,
|
|
8600
|
+
onChanged: (d) => setState(() => _popoverDate = d),
|
|
8601
|
+
locale: locale,
|
|
8602
|
+
),
|
|
8603
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8604
|
+
KasyDatePicker(
|
|
8605
|
+
label: 'Dialog',
|
|
8606
|
+
placeholder: 'Choose a date',
|
|
8607
|
+
value: _dialogDate,
|
|
8608
|
+
onChanged: (d) => setState(() => _dialogDate = d),
|
|
8609
|
+
presentation: KasyDatePickerPresentation.dialog,
|
|
8610
|
+
locale: locale,
|
|
8611
|
+
),
|
|
8612
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8613
|
+
KasyDatePicker(
|
|
8614
|
+
label: 'Bottom sheet',
|
|
8615
|
+
placeholder: 'Choose a date',
|
|
8616
|
+
value: _sheetDate,
|
|
8617
|
+
onChanged: (d) => setState(() => _sheetDate = d),
|
|
8618
|
+
presentation: KasyDatePickerPresentation.bottomSheet,
|
|
8619
|
+
locale: locale,
|
|
8620
|
+
),
|
|
8621
|
+
],
|
|
8622
|
+
);
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
|
|
8626
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8627
|
+
// DatePicker — Range selection (Airbnb-style start + end)
|
|
8628
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8629
|
+
|
|
8630
|
+
Widget _buildDatePickerRange(BuildContext context) =>
|
|
8631
|
+
const _DatePickerRangePreview();
|
|
8632
|
+
|
|
8633
|
+
class _DatePickerRangePreview extends StatefulWidget {
|
|
8634
|
+
const _DatePickerRangePreview();
|
|
8635
|
+
|
|
8636
|
+
@override
|
|
8637
|
+
State<_DatePickerRangePreview> createState() =>
|
|
8638
|
+
_DatePickerRangePreviewState();
|
|
8639
|
+
}
|
|
8640
|
+
|
|
8641
|
+
class _DatePickerRangePreviewState extends State<_DatePickerRangePreview> {
|
|
8642
|
+
KasyDateRange? _stay;
|
|
8643
|
+
KasyDateRange? _project;
|
|
8644
|
+
|
|
8645
|
+
@override
|
|
8646
|
+
Widget build(BuildContext context) {
|
|
8647
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8648
|
+
return Column(
|
|
8649
|
+
mainAxisSize: MainAxisSize.min,
|
|
8650
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8651
|
+
children: [
|
|
8652
|
+
// Classic stay-style picker: tap a check-in date, then a check-out
|
|
8653
|
+
// date. The calendar keeps the start highlighted while waiting for
|
|
8654
|
+
// the end, then auto-closes after the second tap.
|
|
8655
|
+
KasyDatePicker(
|
|
8656
|
+
label: 'Stay',
|
|
8657
|
+
placeholder: 'Check-in -> Check-out',
|
|
8658
|
+
description: 'Tap a start date, then an end date.',
|
|
8659
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8660
|
+
range: _stay,
|
|
8661
|
+
onRangeChanged: (r) => setState(() => _stay = r),
|
|
8662
|
+
locale: locale,
|
|
8663
|
+
),
|
|
8664
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8665
|
+
// Dialog presentation of the same range mode — handy on smaller
|
|
8666
|
+
// screens where the popover may not have enough room.
|
|
8667
|
+
KasyDatePicker(
|
|
8668
|
+
label: 'Project window (dialog)',
|
|
8669
|
+
placeholder: 'Pick start -> end',
|
|
8670
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8671
|
+
range: _project,
|
|
8672
|
+
onRangeChanged: (r) => setState(() => _project = r),
|
|
8673
|
+
presentation: KasyDatePickerPresentation.dialog,
|
|
8674
|
+
locale: locale,
|
|
8675
|
+
),
|
|
8676
|
+
],
|
|
8677
|
+
);
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
|
|
8681
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8682
|
+
// DatePicker — Multi-month (2 or 3 months side by side, Airbnb desktop look)
|
|
8683
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8684
|
+
|
|
8685
|
+
Widget _buildDatePickerMultiMonth(BuildContext context) =>
|
|
8686
|
+
const _DatePickerMultiMonthPreview();
|
|
8687
|
+
|
|
8688
|
+
class _DatePickerMultiMonthPreview extends StatefulWidget {
|
|
8689
|
+
const _DatePickerMultiMonthPreview();
|
|
8690
|
+
|
|
8691
|
+
@override
|
|
8692
|
+
State<_DatePickerMultiMonthPreview> createState() =>
|
|
8693
|
+
_DatePickerMultiMonthPreviewState();
|
|
8694
|
+
}
|
|
8695
|
+
|
|
8696
|
+
class _DatePickerMultiMonthPreviewState
|
|
8697
|
+
extends State<_DatePickerMultiMonthPreview> {
|
|
8698
|
+
KasyDateRange? _stay;
|
|
8699
|
+
DateTime? _single;
|
|
8700
|
+
|
|
8701
|
+
@override
|
|
8702
|
+
Widget build(BuildContext context) {
|
|
8703
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8704
|
+
return Column(
|
|
8705
|
+
mainAxisSize: MainAxisSize.min,
|
|
8706
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8707
|
+
children: [
|
|
8708
|
+
// 3-month layout combined with range selection — the classic
|
|
8709
|
+
// Airbnb / Booking desktop pattern for picking a stay.
|
|
8710
|
+
KasyDatePicker(
|
|
8711
|
+
label: '3 months + range',
|
|
8712
|
+
placeholder: 'Check-in -> Check-out',
|
|
8713
|
+
description: 'Pass monthsToShow: 3 to opt in. Best on wide canvases.',
|
|
8714
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8715
|
+
range: _stay,
|
|
8716
|
+
onRangeChanged: (r) => setState(() => _stay = r),
|
|
8717
|
+
monthsToShow: 3,
|
|
8718
|
+
locale: locale,
|
|
8719
|
+
),
|
|
8720
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8721
|
+
// 2-month layout in single-date mode — useful when there's enough
|
|
8722
|
+
// horizontal room but you still want one tap to commit.
|
|
8723
|
+
KasyDatePicker(
|
|
8724
|
+
label: '2 months + single date',
|
|
8725
|
+
placeholder: 'Pick a date',
|
|
8726
|
+
value: _single,
|
|
8727
|
+
onChanged: (d) => setState(() => _single = d),
|
|
8728
|
+
monthsToShow: 2,
|
|
8729
|
+
locale: locale,
|
|
8730
|
+
),
|
|
8731
|
+
],
|
|
8732
|
+
);
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8736
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8737
|
+
// DatePicker — Variants (filled, no backdrop, no suffix, no focus border)
|
|
8738
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8739
|
+
|
|
8740
|
+
Widget _buildDatePickerVariants(BuildContext context) =>
|
|
8741
|
+
const _DatePickerVariantsPreview();
|
|
8742
|
+
|
|
8743
|
+
class _DatePickerVariantsPreview extends StatefulWidget {
|
|
8744
|
+
const _DatePickerVariantsPreview();
|
|
8745
|
+
|
|
8746
|
+
@override
|
|
8747
|
+
State<_DatePickerVariantsPreview> createState() =>
|
|
8748
|
+
_DatePickerVariantsPreviewState();
|
|
8749
|
+
}
|
|
8750
|
+
|
|
8751
|
+
class _DatePickerVariantsPreviewState
|
|
8752
|
+
extends State<_DatePickerVariantsPreview> {
|
|
8753
|
+
DateTime? _filledDate;
|
|
8754
|
+
DateTime? _noBarrierDate;
|
|
8755
|
+
DateTime? _noSuffixDate;
|
|
8756
|
+
DateTime? _noFocusDate;
|
|
8757
|
+
|
|
8758
|
+
@override
|
|
8759
|
+
Widget build(BuildContext context) {
|
|
8760
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8761
|
+
return Column(
|
|
8762
|
+
mainAxisSize: MainAxisSize.min,
|
|
8763
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8764
|
+
children: [
|
|
8765
|
+
// Filled / flat surface — matches KasyTextField.secondary, useful on
|
|
8766
|
+
// backgrounds where the default drop shadow feels heavy.
|
|
8767
|
+
KasyDatePicker(
|
|
8768
|
+
label: 'Filled (no shadow)',
|
|
8769
|
+
placeholder: 'Choose a date',
|
|
8770
|
+
value: _filledDate,
|
|
8771
|
+
onChanged: (d) => setState(() => _filledDate = d),
|
|
8772
|
+
variant: KasyTextFieldVariant.secondary,
|
|
8773
|
+
locale: locale,
|
|
8774
|
+
),
|
|
8775
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8776
|
+
// Popover without the dimmed backdrop — feels lighter / tooltip-like.
|
|
8777
|
+
KasyDatePicker(
|
|
8778
|
+
label: 'No backdrop',
|
|
8779
|
+
placeholder: 'Choose a date',
|
|
8780
|
+
description: 'Popover floats over the page without a dimmed scrim.',
|
|
8781
|
+
value: _noBarrierDate,
|
|
8782
|
+
onChanged: (d) => setState(() => _noBarrierDate = d),
|
|
8783
|
+
showBarrier: false,
|
|
8784
|
+
locale: locale,
|
|
8785
|
+
),
|
|
8786
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8787
|
+
// No calendar icon in the suffix slot.
|
|
8788
|
+
KasyDatePicker(
|
|
8789
|
+
label: 'No suffix icon',
|
|
8790
|
+
placeholder: 'Choose a date',
|
|
8791
|
+
value: _noSuffixDate,
|
|
8792
|
+
onChanged: (d) => setState(() => _noSuffixDate = d),
|
|
8793
|
+
showSuffix: false,
|
|
8794
|
+
locale: locale,
|
|
8795
|
+
),
|
|
8796
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8797
|
+
// Disables the blue focus outline while the calendar is open — quieter
|
|
8798
|
+
// affordance for very dense forms.
|
|
8799
|
+
KasyDatePicker(
|
|
8800
|
+
label: 'No focus border',
|
|
8801
|
+
placeholder: 'Choose a date',
|
|
8802
|
+
value: _noFocusDate,
|
|
8803
|
+
onChanged: (d) => setState(() => _noFocusDate = d),
|
|
8804
|
+
focusBorder: false,
|
|
8805
|
+
locale: locale,
|
|
8806
|
+
),
|
|
8807
|
+
],
|
|
8808
|
+
);
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8813
|
+
// DatePicker — Date formats
|
|
8814
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8815
|
+
|
|
8816
|
+
Widget _buildDatePickerFormats(BuildContext context) =>
|
|
8817
|
+
const _DatePickerFormatsPreview();
|
|
8818
|
+
|
|
8819
|
+
const List<String> _kEnglishMonthsLong = [
|
|
8820
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
8821
|
+
'July', 'August', 'September', 'October', 'November', 'December',
|
|
8822
|
+
];
|
|
8823
|
+
|
|
8824
|
+
String _longUsFormat(DateTime d) =>
|
|
8825
|
+
'${_kEnglishMonthsLong[d.month - 1]} ${d.day}, ${d.year}';
|
|
8826
|
+
|
|
8827
|
+
class _DatePickerFormatsPreview extends StatefulWidget {
|
|
8828
|
+
const _DatePickerFormatsPreview();
|
|
8829
|
+
|
|
8830
|
+
@override
|
|
8831
|
+
State<_DatePickerFormatsPreview> createState() =>
|
|
8832
|
+
_DatePickerFormatsPreviewState();
|
|
8833
|
+
}
|
|
8834
|
+
|
|
8835
|
+
// Each tab maps to one preset (or to the "Auto" / "Custom" entries). The
|
|
8836
|
+
// `format` field is null for the two non-preset entries; the build method
|
|
8837
|
+
// branches on those.
|
|
8838
|
+
class _DatePickerFormatOption {
|
|
8839
|
+
const _DatePickerFormatOption({
|
|
8840
|
+
required this.label,
|
|
8841
|
+
required this.description,
|
|
8842
|
+
this.format,
|
|
8843
|
+
this.useAppLocale = false,
|
|
8844
|
+
this.customFormat,
|
|
8845
|
+
});
|
|
8846
|
+
|
|
8847
|
+
final String label;
|
|
8848
|
+
final String description;
|
|
8849
|
+
final KasyDateFormat? format;
|
|
8850
|
+
final bool useAppLocale;
|
|
8851
|
+
final String Function(DateTime)? customFormat;
|
|
8852
|
+
}
|
|
8853
|
+
|
|
8854
|
+
const List<_DatePickerFormatOption> _kDatePickerFormatOptions = [
|
|
8855
|
+
_DatePickerFormatOption(
|
|
8856
|
+
label: 'Auto',
|
|
8857
|
+
description: 'Follows the active app language (US, South American, …).',
|
|
8858
|
+
useAppLocale: true,
|
|
8859
|
+
),
|
|
8860
|
+
_DatePickerFormatOption(
|
|
8861
|
+
label: 'US',
|
|
8862
|
+
description: 'MM / DD / YYYY — United States.',
|
|
8863
|
+
format: KasyDateFormat.us,
|
|
8864
|
+
),
|
|
8865
|
+
_DatePickerFormatOption(
|
|
8866
|
+
label: 'South American',
|
|
8867
|
+
description: 'DD / MM / YYYY — South America, UK, Spain, Portugal.',
|
|
8868
|
+
format: KasyDateFormat.southAmerican,
|
|
8869
|
+
),
|
|
8870
|
+
_DatePickerFormatOption(
|
|
8871
|
+
label: 'European',
|
|
8872
|
+
description: 'DD.MM.YYYY — Germany, Austria, Switzerland.',
|
|
8873
|
+
format: KasyDateFormat.european,
|
|
8874
|
+
),
|
|
8875
|
+
_DatePickerFormatOption(
|
|
8876
|
+
label: 'ISO 8601',
|
|
8877
|
+
description: 'YYYY-MM-DD — international standard.',
|
|
8878
|
+
format: KasyDateFormat.iso,
|
|
8879
|
+
),
|
|
8880
|
+
_DatePickerFormatOption(
|
|
8881
|
+
label: 'East Asian',
|
|
8882
|
+
description: 'YYYY / MM / DD — Japan, China, Korea.',
|
|
8883
|
+
format: KasyDateFormat.eastAsian,
|
|
8884
|
+
),
|
|
8885
|
+
_DatePickerFormatOption(
|
|
8886
|
+
label: 'Custom',
|
|
8887
|
+
description: 'Any layout via the formatDate callback (long form here).',
|
|
8888
|
+
customFormat: _longUsFormat,
|
|
8889
|
+
),
|
|
8890
|
+
];
|
|
8891
|
+
|
|
8892
|
+
class _DatePickerFormatsPreviewState extends State<_DatePickerFormatsPreview> {
|
|
8893
|
+
int _tab = 0;
|
|
8894
|
+
DateTime _date = DateTime(2026, 5, 12);
|
|
8895
|
+
|
|
8896
|
+
@override
|
|
8897
|
+
Widget build(BuildContext context) {
|
|
8898
|
+
final _DatePickerFormatOption option = _kDatePickerFormatOptions[_tab];
|
|
8899
|
+
final KasyDatePickerLocale appLocale = _datePickerLocaleForApp(context);
|
|
8900
|
+
|
|
8901
|
+
return Column(
|
|
8902
|
+
mainAxisSize: MainAxisSize.min,
|
|
8903
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8904
|
+
children: [
|
|
8905
|
+
// Secondary (underline) variant keeps the format switcher visually
|
|
8906
|
+
// light on phones — pill tabs were too bulky for a switch with many
|
|
8907
|
+
// labels and hug mode lets the row scroll horizontally when needed.
|
|
8908
|
+
KasyTabs(
|
|
8909
|
+
tabs: [
|
|
8910
|
+
for (final o in _kDatePickerFormatOptions) o.label,
|
|
8911
|
+
],
|
|
8912
|
+
selectedIndex: _tab,
|
|
8913
|
+
onTabSelected: (i) => setState(() => _tab = i),
|
|
8914
|
+
variant: KasyTabsVariant.secondary,
|
|
8915
|
+
),
|
|
8916
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8917
|
+
KasyDatePicker(
|
|
8918
|
+
label: option.label,
|
|
8919
|
+
description: option.description,
|
|
8920
|
+
value: _date,
|
|
8921
|
+
onChanged: (d) => setState(() => _date = d),
|
|
8922
|
+
locale: option.useAppLocale ? appLocale : KasyDatePickerLocale.en,
|
|
8923
|
+
dateFormat: option.format,
|
|
8924
|
+
formatDate: option.customFormat,
|
|
8925
|
+
),
|
|
8926
|
+
],
|
|
8927
|
+
);
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8931
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8932
|
+
// DatePicker — Field states
|
|
8933
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8934
|
+
|
|
8935
|
+
Widget _buildDatePickerFieldStates(BuildContext context) =>
|
|
8936
|
+
const _DatePickerFieldStatesPreview();
|
|
8937
|
+
|
|
8938
|
+
const List<String> _kEnglishMonthAbbrev = [
|
|
8939
|
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
8940
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
|
8941
|
+
];
|
|
8942
|
+
|
|
8943
|
+
String _abbrevFormat(DateTime d) =>
|
|
8944
|
+
'${_kEnglishMonthAbbrev[d.month - 1]} ${d.day}, ${d.year}';
|
|
8945
|
+
|
|
8946
|
+
class _DatePickerFieldStatesPreview extends StatefulWidget {
|
|
8947
|
+
const _DatePickerFieldStatesPreview();
|
|
8948
|
+
|
|
8949
|
+
@override
|
|
8950
|
+
State<_DatePickerFieldStatesPreview> createState() =>
|
|
8951
|
+
_DatePickerFieldStatesPreviewState();
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
class _DatePickerFieldStatesPreviewState
|
|
8955
|
+
extends State<_DatePickerFieldStatesPreview> {
|
|
8956
|
+
DateTime? _deadline;
|
|
8957
|
+
DateTime? _shipDate;
|
|
8958
|
+
final DateTime _disabledDate = DateTime(2026, 6, 15);
|
|
8959
|
+
|
|
8960
|
+
@override
|
|
8961
|
+
Widget build(BuildContext context) {
|
|
8962
|
+
return Column(
|
|
8963
|
+
mainAxisSize: MainAxisSize.min,
|
|
8964
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8965
|
+
children: [
|
|
8966
|
+
KasyDatePicker(
|
|
8967
|
+
label: 'Deadline',
|
|
8968
|
+
placeholder: 'Select a deadline',
|
|
8969
|
+
showRequiredIndicator: true,
|
|
8970
|
+
description: 'Required for the project timeline.',
|
|
8971
|
+
value: _deadline,
|
|
8972
|
+
onChanged: (d) => setState(() => _deadline = d),
|
|
8973
|
+
),
|
|
8974
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8975
|
+
KasyDatePicker(
|
|
8976
|
+
label: 'Ship date',
|
|
8977
|
+
placeholder: 'Choose a date',
|
|
8978
|
+
errorText: 'Please select a valid ship date.',
|
|
8979
|
+
value: _shipDate,
|
|
8980
|
+
onChanged: (d) => setState(() => _shipDate = d),
|
|
8981
|
+
),
|
|
8982
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8983
|
+
KasyDatePicker(
|
|
8984
|
+
label: 'Disabled date',
|
|
8985
|
+
enabled: false,
|
|
8986
|
+
value: _disabledDate,
|
|
8987
|
+
formatDate: _abbrevFormat,
|
|
8988
|
+
description: 'The date cannot be changed when the order is locked.',
|
|
8989
|
+
),
|
|
8990
|
+
],
|
|
8991
|
+
);
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|
|
2
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
4
4
|
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
5
|
-
import 'package:kasy_kit/core/bottom_menu/kasy_bart_navigation.dart';
|
|
6
5
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
7
6
|
import 'package:kasy_kit/core/states/components/maybe_ask_rating.dart';
|
|
8
7
|
import 'package:kasy_kit/core/states/components/maybe_show_update_bottom_sheet.dart';
|
|
@@ -60,13 +59,9 @@ class HomePage extends ConsumerWidget {
|
|
|
60
59
|
gradient: _featuresGradient(context, isDark),
|
|
61
60
|
onOpen: () {
|
|
62
61
|
KasyHaptics.medium(context);
|
|
63
|
-
Navigator.of(context)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (context.mounted) {
|
|
67
|
-
kasyShowBottomBar(context);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
62
|
+
Navigator.of(context).pushNamed(
|
|
63
|
+
bartInnerPath('home', 'features'),
|
|
64
|
+
);
|
|
70
65
|
},
|
|
71
66
|
);
|
|
72
67
|
final componentsCard = _DashboardGradientCard(
|
|
@@ -79,15 +74,9 @@ class HomePage extends ConsumerWidget {
|
|
|
79
74
|
gradient: _componentsGradient(context, isDark),
|
|
80
75
|
onOpen: () {
|
|
81
76
|
KasyHaptics.medium(context);
|
|
82
|
-
Navigator.of(context)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
.then((_) {
|
|
87
|
-
if (context.mounted) {
|
|
88
|
-
kasyShowBottomBar(context);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
77
|
+
Navigator.of(context).pushNamed(
|
|
78
|
+
bartInnerPath('home', 'components'),
|
|
79
|
+
);
|
|
91
80
|
},
|
|
92
81
|
);
|
|
93
82
|
if (isWide) {
|
|
@@ -135,10 +124,7 @@ class HomePage extends ConsumerWidget {
|
|
|
135
124
|
}
|
|
136
125
|
|
|
137
126
|
/// Soft product-style gradients (calm blues / lilac); same geometry, distinct hue.
|
|
138
|
-
static LinearGradient _componentsGradient(
|
|
139
|
-
BuildContext context,
|
|
140
|
-
bool isDark,
|
|
141
|
-
) {
|
|
127
|
+
static LinearGradient _componentsGradient(BuildContext context, bool isDark) {
|
|
142
128
|
if (isDark) {
|
|
143
129
|
return LinearGradient(
|
|
144
130
|
begin: Alignment.topLeft,
|
|
@@ -153,11 +139,7 @@ class HomePage extends ConsumerWidget {
|
|
|
153
139
|
return const LinearGradient(
|
|
154
140
|
begin: Alignment.topLeft,
|
|
155
141
|
end: Alignment.bottomRight,
|
|
156
|
-
colors: [
|
|
157
|
-
Color(0xFFDCE6FF),
|
|
158
|
-
Color(0xFFF0F6FF),
|
|
159
|
-
Color(0xFFE2E8F6),
|
|
160
|
-
],
|
|
142
|
+
colors: [Color(0xFFDCE6FF), Color(0xFFF0F6FF), Color(0xFFE2E8F6)],
|
|
161
143
|
);
|
|
162
144
|
}
|
|
163
145
|
|
|
@@ -176,11 +158,7 @@ class HomePage extends ConsumerWidget {
|
|
|
176
158
|
return const LinearGradient(
|
|
177
159
|
begin: Alignment.topLeft,
|
|
178
160
|
end: Alignment.bottomRight,
|
|
179
|
-
colors: [
|
|
180
|
-
Color(0xFFE2D8FC),
|
|
181
|
-
Color(0xFFF5ECFA),
|
|
182
|
-
Color(0xFFDCE8FF),
|
|
183
|
-
],
|
|
161
|
+
colors: [Color(0xFFE2D8FC), Color(0xFFF5ECFA), Color(0xFFDCE8FF)],
|
|
184
162
|
);
|
|
185
163
|
}
|
|
186
164
|
}
|
|
@@ -205,22 +183,23 @@ class _DashboardGradientCard extends StatelessWidget {
|
|
|
205
183
|
@override
|
|
206
184
|
Widget build(BuildContext context) {
|
|
207
185
|
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
|
208
|
-
final Color titleColor =
|
|
209
|
-
isDark ? Colors.white : const Color(0xFF1A1D26);
|
|
186
|
+
final Color titleColor = isDark ? Colors.white : const Color(0xFF1A1D26);
|
|
210
187
|
final Color subtitleColor = titleColor.withValues(alpha: 0.62);
|
|
211
188
|
final Color arrowCircleColor = isDark
|
|
212
189
|
? Colors.black.withValues(alpha: 0.38)
|
|
213
190
|
: Colors.white.withValues(alpha: 0.82);
|
|
214
|
-
final Color arrowIconColor =
|
|
215
|
-
|
|
191
|
+
final Color arrowIconColor = isDark
|
|
192
|
+
? Colors.white
|
|
193
|
+
: const Color(0xFF1A1D26);
|
|
216
194
|
final Color countBadgeFill = isDark
|
|
217
195
|
? Colors.white.withValues(alpha: 0.14)
|
|
218
196
|
: Colors.white.withValues(alpha: 0.38);
|
|
219
197
|
final Color countBadgeBorder = isDark
|
|
220
198
|
? Colors.white.withValues(alpha: 0.24)
|
|
221
199
|
: Colors.black.withValues(alpha: 0.06);
|
|
222
|
-
final Color countBadgeLabelColor =
|
|
223
|
-
|
|
200
|
+
final Color countBadgeLabelColor = isDark
|
|
201
|
+
? Colors.white.withValues(alpha: 0.94)
|
|
202
|
+
: countBadgeTextColor;
|
|
224
203
|
final Color cardOutlineColor = context.colors.outline.withValues(
|
|
225
204
|
alpha: isDark ? 0.34 : 0.30,
|
|
226
205
|
);
|
|
@@ -246,9 +225,7 @@ class _DashboardGradientCard extends StatelessWidget {
|
|
|
246
225
|
child: DecoratedBox(
|
|
247
226
|
decoration: BoxDecoration(
|
|
248
227
|
color: countBadgeFill,
|
|
249
|
-
borderRadius: BorderRadius.circular(
|
|
250
|
-
KasyRadius.full,
|
|
251
|
-
),
|
|
228
|
+
borderRadius: BorderRadius.circular(KasyRadius.full),
|
|
252
229
|
border: Border.all(color: countBadgeBorder),
|
|
253
230
|
),
|
|
254
231
|
child: Padding(
|