@underpeaks/generator-flutter 1.0.31 → 1.0.35

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.
Files changed (141) hide show
  1. package/dist/adapters/bloc/bloc.js +1 -1
  2. package/dist/adapters/provider/provider.js +1 -1
  3. package/dist/adapters/riverpod/provider.js +1 -1
  4. package/dist/generator.d.ts +0 -10
  5. package/dist/generator.d.ts.map +1 -1
  6. package/dist/generator.js +75 -45
  7. package/dist/generator.js.map +1 -1
  8. package/dist/templates/env.d.ts.map +1 -1
  9. package/dist/templates/env.js +7 -0
  10. package/dist/templates/env.js.map +1 -1
  11. package/dist/templates/main.d.ts.map +1 -1
  12. package/dist/templates/main.js +3 -4
  13. package/dist/templates/main.js.map +1 -1
  14. package/dist/templates/model.d.ts.map +1 -1
  15. package/dist/templates/model.js +36 -73
  16. package/dist/templates/model.js.map +1 -1
  17. package/dist/templates/navigation.d.ts +1 -0
  18. package/dist/templates/navigation.d.ts.map +1 -1
  19. package/dist/templates/navigation.js +27 -26
  20. package/dist/templates/navigation.js.map +1 -1
  21. package/dist/templates/page-blog-post.d.ts +2 -2
  22. package/dist/templates/page-blog-post.d.ts.map +1 -1
  23. package/dist/templates/page-blog-post.js +49 -2
  24. package/dist/templates/page-blog-post.js.map +1 -1
  25. package/dist/templates/page-calendar.d.ts +2 -2
  26. package/dist/templates/page-calendar.d.ts.map +1 -1
  27. package/dist/templates/page-calendar.js +50 -2
  28. package/dist/templates/page-calendar.js.map +1 -1
  29. package/dist/templates/page-cart.d.ts +2 -2
  30. package/dist/templates/page-cart.d.ts.map +1 -1
  31. package/dist/templates/page-cart.js +32 -4
  32. package/dist/templates/page-cart.js.map +1 -1
  33. package/dist/templates/page-checkout.d.ts +2 -2
  34. package/dist/templates/page-checkout.d.ts.map +1 -1
  35. package/dist/templates/page-checkout.js +22 -2
  36. package/dist/templates/page-checkout.js.map +1 -1
  37. package/dist/templates/page-contact.d.ts +1 -0
  38. package/dist/templates/page-contact.js +1 -0
  39. package/dist/templates/page-contact.js.map +1 -1
  40. package/dist/templates/page-custom.d.ts +2 -2
  41. package/dist/templates/page-custom.d.ts.map +1 -1
  42. package/dist/templates/page-custom.js +50 -2
  43. package/dist/templates/page-custom.js.map +1 -1
  44. package/dist/templates/page-dashboard.d.ts +2 -2
  45. package/dist/templates/page-dashboard.d.ts.map +1 -1
  46. package/dist/templates/page-dashboard.js +102 -60
  47. package/dist/templates/page-dashboard.js.map +1 -1
  48. package/dist/templates/page-detail.d.ts +2 -2
  49. package/dist/templates/page-detail.d.ts.map +1 -1
  50. package/dist/templates/page-detail.js +188 -18
  51. package/dist/templates/page-detail.js.map +1 -1
  52. package/dist/templates/page-feed.d.ts +2 -2
  53. package/dist/templates/page-feed.d.ts.map +1 -1
  54. package/dist/templates/page-feed.js +40 -3
  55. package/dist/templates/page-feed.js.map +1 -1
  56. package/dist/templates/page-form.d.ts.map +1 -1
  57. package/dist/templates/page-form.js +779 -72
  58. package/dist/templates/page-form.js.map +1 -1
  59. package/dist/templates/page-gallery.d.ts +2 -2
  60. package/dist/templates/page-gallery.d.ts.map +1 -1
  61. package/dist/templates/page-gallery.js +61 -4
  62. package/dist/templates/page-gallery.js.map +1 -1
  63. package/dist/templates/page-invoice.d.ts +2 -2
  64. package/dist/templates/page-invoice.d.ts.map +1 -1
  65. package/dist/templates/page-invoice.js +45 -2
  66. package/dist/templates/page-invoice.js.map +1 -1
  67. package/dist/templates/page-kanban.d.ts +2 -2
  68. package/dist/templates/page-kanban.d.ts.map +1 -1
  69. package/dist/templates/page-kanban.js +112 -55
  70. package/dist/templates/page-kanban.js.map +1 -1
  71. package/dist/templates/page-landing-hero-cta.d.ts +2 -2
  72. package/dist/templates/page-landing-hero-cta.d.ts.map +1 -1
  73. package/dist/templates/page-landing-hero-cta.js +55 -4
  74. package/dist/templates/page-landing-hero-cta.js.map +1 -1
  75. package/dist/templates/page-landing-hero-features.d.ts +2 -2
  76. package/dist/templates/page-landing-hero-features.d.ts.map +1 -1
  77. package/dist/templates/page-landing-hero-features.js +32 -2
  78. package/dist/templates/page-landing-hero-features.js.map +1 -1
  79. package/dist/templates/page-landing-hero-pricing.d.ts +2 -2
  80. package/dist/templates/page-landing-hero-pricing.d.ts.map +1 -1
  81. package/dist/templates/page-landing-hero-pricing.js +32 -2
  82. package/dist/templates/page-landing-hero-pricing.js.map +1 -1
  83. package/dist/templates/page-landing-minimal.d.ts +2 -2
  84. package/dist/templates/page-landing-minimal.d.ts.map +1 -1
  85. package/dist/templates/page-landing-minimal.js +32 -2
  86. package/dist/templates/page-landing-minimal.js.map +1 -1
  87. package/dist/templates/page-list.d.ts +2 -2
  88. package/dist/templates/page-list.d.ts.map +1 -1
  89. package/dist/templates/page-list.js +72 -6
  90. package/dist/templates/page-list.js.map +1 -1
  91. package/dist/templates/page-messages.d.ts +2 -2
  92. package/dist/templates/page-messages.d.ts.map +1 -1
  93. package/dist/templates/page-messages.js +27 -2
  94. package/dist/templates/page-messages.js.map +1 -1
  95. package/dist/templates/page-onboarding.d.ts +2 -2
  96. package/dist/templates/page-onboarding.d.ts.map +1 -1
  97. package/dist/templates/page-onboarding.js +48 -3
  98. package/dist/templates/page-onboarding.js.map +1 -1
  99. package/dist/templates/page-otp.d.ts +2 -2
  100. package/dist/templates/page-otp.d.ts.map +1 -1
  101. package/dist/templates/page-otp.js +31 -3
  102. package/dist/templates/page-otp.js.map +1 -1
  103. package/dist/templates/page-profile.d.ts +2 -2
  104. package/dist/templates/page-profile.d.ts.map +1 -1
  105. package/dist/templates/page-profile.js +42 -2
  106. package/dist/templates/page-profile.js.map +1 -1
  107. package/dist/templates/page-reset-password.d.ts +2 -2
  108. package/dist/templates/page-reset-password.d.ts.map +1 -1
  109. package/dist/templates/page-reset-password.js +18 -2
  110. package/dist/templates/page-reset-password.js.map +1 -1
  111. package/dist/templates/page-search.d.ts +2 -2
  112. package/dist/templates/page-search.d.ts.map +1 -1
  113. package/dist/templates/page-search.js +63 -21
  114. package/dist/templates/page-search.js.map +1 -1
  115. package/dist/templates/page-settings.d.ts +2 -2
  116. package/dist/templates/page-settings.d.ts.map +1 -1
  117. package/dist/templates/page-settings.js +43 -5
  118. package/dist/templates/page-settings.js.map +1 -1
  119. package/dist/templates/page-splash.d.ts +2 -2
  120. package/dist/templates/page-splash.d.ts.map +1 -1
  121. package/dist/templates/page-splash.js +27 -7
  122. package/dist/templates/page-splash.js.map +1 -1
  123. package/dist/templates/page-wallet.d.ts +2 -2
  124. package/dist/templates/page-wallet.d.ts.map +1 -1
  125. package/dist/templates/page-wallet.js +50 -16
  126. package/dist/templates/page-wallet.js.map +1 -1
  127. package/dist/templates/pubspec.d.ts +1 -1
  128. package/dist/templates/pubspec.d.ts.map +1 -1
  129. package/dist/templates/pubspec.js +26 -1
  130. package/dist/templates/pubspec.js.map +1 -1
  131. package/dist/templates/repository.d.ts.map +1 -1
  132. package/dist/templates/repository.js +1 -1
  133. package/dist/templates/repository.js.map +1 -1
  134. package/dist/templates/widget-card.d.ts +1 -1
  135. package/dist/templates/widget-card.d.ts.map +1 -1
  136. package/dist/templates/widget-card.js +84 -39
  137. package/dist/templates/widget-card.js.map +1 -1
  138. package/dist/widgets/registry-display.d.ts.map +1 -1
  139. package/dist/widgets/registry-display.js +6 -5
  140. package/dist/widgets/registry-display.js.map +1 -1
  141. package/package.json +1 -1
@@ -3,13 +3,416 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.generateFormPage = generateFormPage;
5
5
  const generator_core_1 = require("@underpeaks/generator-core");
6
+ const generator_core_2 = require("@underpeaks/generator-core");
7
+ const UI_TYPE_WIDGET_INFO = {
8
+ 'textfield': { category: 'text', file: 'nxf_textfield', cls: 'NxfTextfield', kind: 'string' },
9
+ 'textarea': { category: 'text', file: 'nxf_textarea', cls: 'NxfTextarea', kind: 'string' },
10
+ 'rich-text': { category: 'text', file: 'nxf_rich_text', cls: 'NxfRichText', kind: 'string' },
11
+ 'markdown-editor': { category: 'text', file: 'nxf_markdown_editor', cls: 'NxfMarkdownEditor', kind: 'string' },
12
+ 'code-editor': { category: 'text', file: 'nxf_code_editor', cls: 'NxfCodeEditor', kind: 'string' },
13
+ 'html-editor': { category: 'text', file: 'nxf_html_editor', cls: 'NxfHtmlEditor', kind: 'string' },
14
+ 'email-input': { category: 'text', file: 'nxf_email_input', cls: 'NxfEmailInput', kind: 'string' },
15
+ 'phone-input': { category: 'text', file: 'nxf_phone_input', cls: 'NxfPhoneInput', kind: 'string' },
16
+ 'password-input': { category: 'text', file: 'nxf_password_input', cls: 'NxfPasswordInput', kind: 'string' },
17
+ 'slug-input': { category: 'text', file: 'nxf_slug_input', cls: 'NxfSlugInput', kind: 'string' },
18
+ 'search-input': { category: 'text', file: 'nxf_search_input', cls: 'NxfSearchInput', kind: 'string' },
19
+ 'otp-input': { category: 'text', file: 'nxf_otp_input', cls: 'NxfOtpInput', kind: 'string-no-value' },
20
+ 'masked-input': { category: 'text', file: 'nxf_masked_input', cls: 'NxfMaskedInput', kind: 'string' },
21
+ 'color-picker': { category: 'text', file: 'nxf_color_picker', cls: 'NxfColorPicker', kind: 'string-no-required' },
22
+ 'url-input': { category: 'text', file: 'nxf_url_input', cls: 'NxfUrlInput', kind: 'string' },
23
+ 'barcode-input': { category: 'text', file: 'nxf_barcode_input', cls: 'NxfBarcodeInput', kind: 'string' },
24
+ 'qr-code-input': { category: 'text', file: 'nxf_qr_code_input', cls: 'NxfQrCodeInput', kind: 'string' },
25
+ 'select': { category: 'choice', file: 'nxf_select', cls: 'NxfSelect', kind: 'string-select' },
26
+ 'multi-select': { category: 'choice', file: 'nxf_multi_select', cls: 'NxfMultiSelect', kind: 'string-list-opts' },
27
+ 'radio': { category: 'choice', file: 'nxf_radio', cls: 'NxfRadio', kind: 'string-select' },
28
+ 'checkbox-group': { category: 'choice', file: 'nxf_checkbox_group', cls: 'NxfCheckboxGroup', kind: 'string-list-opts' },
29
+ 'autocomplete': { category: 'choice', file: 'nxf_autocomplete', cls: 'NxfAutocomplete', kind: 'string-required' },
30
+ 'number-input': { category: 'numeric', file: 'nxf_number_input', cls: 'NxfNumberInput', kind: 'num' },
31
+ 'slider': { category: 'numeric', file: 'nxf_slider', cls: 'NxfSlider', kind: 'double-req' },
32
+ 'range-slider': { category: 'numeric', file: 'nxf_range_slider', cls: 'NxfRangeSlider', kind: 'range' },
33
+ 'rating': { category: 'numeric', file: 'nxf_rating', cls: 'NxfRating', kind: 'int-req' },
34
+ 'stepper': { category: 'numeric', file: 'nxf_stepper', cls: 'NxfStepper', kind: 'int-req' },
35
+ 'counter': { category: 'numeric', file: 'nxf_counter', cls: 'NxfCounter', kind: 'int-req' },
36
+ 'percentage-input': { category: 'numeric', file: 'nxf_percentage_input', cls: 'NxfPercentageInput', kind: 'num' },
37
+ 'currency-input': { category: 'numeric', file: 'nxf_currency_input', cls: 'NxfCurrencyInput', kind: 'num' },
38
+ 'weight-input': { category: 'numeric', file: 'nxf_weight_input', cls: 'NxfWeightInput', kind: 'num' },
39
+ 'distance-input': { category: 'numeric', file: 'nxf_distance_input', cls: 'NxfDistanceInput', kind: 'num' },
40
+ 'toggle': { category: 'boolean', file: 'nxf_toggle', cls: 'NxfToggle', kind: 'bool' },
41
+ 'checkbox': { category: 'boolean', file: 'nxf_checkbox', cls: 'NxfCheckbox', kind: 'bool' },
42
+ 'radio-yes-no': { category: 'boolean', file: 'nxf_radio_yes_no', cls: 'NxfRadioYesNo', kind: 'bool' },
43
+ 'switch': { category: 'boolean', file: 'nxf_switch', cls: 'NxfSwitch', kind: 'bool' },
44
+ 'date-picker': { category: 'datetime', file: 'nxf_date_picker', cls: 'NxfDatePicker', kind: 'datetime' },
45
+ 'time-picker': { category: 'datetime', file: 'nxf_time_picker', cls: 'NxfTimePicker', kind: 'time' },
46
+ 'datetime-picker': { category: 'datetime', file: 'nxf_datetime_picker', cls: 'NxfDatetimePicker', kind: 'datetime' },
47
+ 'date-range-picker': { category: 'datetime', file: 'nxf_date_range_picker', cls: 'NxfDateRangePicker', kind: 'daterange' },
48
+ 'calendar-picker': { category: 'datetime', file: 'nxf_calendar_picker', cls: 'NxfCalendarPicker', kind: 'datetime-no-required' },
49
+ 'week-picker': { category: 'datetime', file: 'nxf_week_picker', cls: 'NxfWeekPicker', kind: 'datetime' },
50
+ 'month-picker': { category: 'datetime', file: 'nxf_month_picker', cls: 'NxfMonthPicker', kind: 'datetime' },
51
+ 'relative-date-input': { category: 'datetime', file: 'nxf_relative_date_input', cls: 'NxfRelativeDateInput', kind: 'relative-date' },
52
+ 'hidden': { category: 'identifier', file: 'nxf_hidden', cls: 'NxfHidden', kind: 'hidden' },
53
+ 'key-value-editor': { category: 'structured_data', file: 'nxf_key_value_editor', cls: 'NxfKeyValueEditor', kind: 'map' },
54
+ 'json-viewer': { category: 'structured_data', file: 'nxf_json_viewer', cls: 'NxfJsonViewer', kind: 'string' },
55
+ 'schema-editor': { category: 'structured_data', file: 'nxf_schema_editor', cls: 'NxfSchemaEditor', kind: 'string' },
56
+ 'form-builder': { category: 'structured_data', file: 'nxf_form_builder', cls: 'NxfFormBuilder', kind: 'map' },
57
+ 'tags-input': { category: 'lists', file: 'nxf_tags_input', cls: 'NxfTagsInput', kind: 'string-list' },
58
+ 'checklist': { category: 'lists', file: 'nxf_checklist', cls: 'NxfChecklist', kind: 'checklist' },
59
+ 'list': { category: 'lists', file: 'nxf_list', cls: 'NxfList', kind: 'string-list' },
60
+ 'repeater': { category: 'lists', file: 'nxf_repeater', cls: 'NxfRepeater', kind: 'repeater' },
61
+ 'chip-list-input': { category: 'lists', file: 'nxf_chip_list_input', cls: 'NxfChipListInput', kind: 'string-list' },
62
+ 'ordered-list-input': { category: 'lists', file: 'nxf_ordered_list_input', cls: 'NxfOrderedListInput', kind: 'string-list' },
63
+ 'file-list-upload': { category: 'files', file: 'nxf_file_list_upload', cls: 'NxfFileListUpload', kind: 'upload-multi', uploadParam: 'onPickFiles' },
64
+ 'image-upload': { category: 'upload', file: 'nxf_image_upload', cls: 'NxfImageUpload', kind: 'upload-single', uploadParam: 'onPickImage' },
65
+ 'image-gallery-upload': { category: 'upload', file: 'nxf_image_gallery_upload', cls: 'NxfImageGalleryUpload', kind: 'upload-multi', uploadParam: 'onPickImages' },
66
+ 'avatar-upload': { category: 'upload', file: 'nxf_avatar_upload', cls: 'NxfAvatarUpload', kind: 'upload-single', uploadParam: 'onPickImage' },
67
+ 'cover-image-upload': { category: 'upload', file: 'nxf_cover_image_upload', cls: 'NxfCoverImageUpload', kind: 'upload-single', uploadParam: 'onPickImage' },
68
+ 'image-url': { category: 'reference', file: 'nxf_image_url', cls: 'NxfImageUrl', kind: 'string' },
69
+ 'media-picker': { category: 'reference', file: 'nxf_media_picker', cls: 'NxfMediaPicker', kind: 'upload-single', uploadParam: 'onPickFromLibrary' },
70
+ 'icon-picker': { category: 'reference', file: 'nxf_icon_picker', cls: 'NxfIconPicker', kind: 'string-required' },
71
+ 'file-upload': { category: 'documents', file: 'nxf_file_upload', cls: 'NxfFileUpload', kind: 'upload-single', uploadParam: 'onPickFile' },
72
+ 'document-upload': { category: 'documents', file: 'nxf_document_upload', cls: 'NxfDocumentUpload', kind: 'upload-single', uploadParam: 'onPickDocument' },
73
+ 'video-upload': { category: 'media', file: 'nxf_video_upload', cls: 'NxfVideoUpload', kind: 'upload-single', uploadParam: 'onPickVideo' },
74
+ 'audio-upload': { category: 'media', file: 'nxf_audio_upload', cls: 'NxfAudioUpload', kind: 'upload-single', uploadParam: 'onPickAudio' },
75
+ 'signature-pad': { category: 'other', file: 'nxf_signature_pad', cls: 'NxfSignaturePad', kind: 'signature' },
76
+ 'location-input': { category: 'location', file: 'nxf_location_input', cls: 'NxfLocationInput', kind: 'location' },
77
+ };
78
+ function getWidgetInfo(col) {
79
+ return UI_TYPE_WIDGET_INFO[col.ui_type ?? ''] ?? UI_TYPE_WIDGET_INFO['textfield'];
80
+ }
81
+ function stateType(kind) {
82
+ switch (kind) {
83
+ case 'string-list':
84
+ case 'string-list-opts':
85
+ return 'List<String>';
86
+ case 'num': return 'num?';
87
+ case 'double-req': return 'double';
88
+ case 'range': return 'RangeValues';
89
+ case 'int-req': return 'int';
90
+ case 'bool': return 'bool';
91
+ case 'datetime':
92
+ case 'datetime-no-required': return 'DateTime?';
93
+ case 'time': return 'TimeOfDay?';
94
+ case 'daterange': return 'DateTimeRange?';
95
+ case 'map': return 'Map<String, String>';
96
+ case 'checklist': return 'List<NxfChecklistItem>';
97
+ case 'repeater': return 'List<Map<String, String>>';
98
+ case 'upload-multi': return 'List<String>';
99
+ case 'location': return 'NxfGeoValue?';
100
+ case 'signature': return 'List<Offset?>';
101
+ case 'relative-date': return 'int';
102
+ default: return 'String?';
103
+ }
104
+ }
105
+ function defaultExpr(kind) {
106
+ switch (kind) {
107
+ case 'string-list':
108
+ case 'string-list-opts': return '[]';
109
+ case 'num': return 'null';
110
+ case 'double-req': return '0';
111
+ case 'range': return 'const RangeValues(0, 100)';
112
+ case 'int-req': return '0';
113
+ case 'bool': return 'false';
114
+ case 'map': return '{}';
115
+ case 'checklist': return '[]';
116
+ case 'repeater': return '[]';
117
+ case 'upload-multi': return '[]';
118
+ case 'signature': return '[]';
119
+ case 'relative-date': return '0';
120
+ default: return 'null';
121
+ }
122
+ }
123
+ function buildInitExpr(col, kind) {
124
+ const base = `widget.initial?.${col.name}`;
125
+ switch (kind) {
126
+ case 'string':
127
+ case 'string-no-required':
128
+ case 'string-no-value':
129
+ case 'string-required':
130
+ case 'string-select':
131
+ case 'upload-single':
132
+ case 'hidden':
133
+ return `${base}?.toString()`;
134
+ case 'string-list':
135
+ case 'string-list-opts':
136
+ case 'upload-multi':
137
+ return `${base} is List ? List<String>.from(${base} as List) : []`;
138
+ case 'num':
139
+ return `${base} as num?`;
140
+ case 'double-req':
141
+ return `(${base} as num?)?.toDouble() ?? 0`;
142
+ case 'int-req':
143
+ case 'relative-date':
144
+ return `(${base} as num?)?.toInt() ?? 0`;
145
+ case 'bool':
146
+ return `${base} as bool? ?? false`;
147
+ case 'datetime':
148
+ case 'datetime-no-required':
149
+ return `${base} != null ? DateTime.tryParse(${base}.toString()) : null`;
150
+ default:
151
+ return 'null // TODO: this field type is not auto-populated from an existing record yet';
152
+ }
153
+ }
154
+ function buildSubmitExpr(col, kind) {
155
+ switch (kind) {
156
+ case 'datetime':
157
+ case 'datetime-no-required':
158
+ case 'relative-date':
159
+ return `${col.name}: _${col.name}?.toIso8601String(),`;
160
+ case 'signature':
161
+ return `// NOTE: ${col.name} (signature-pad) is not yet included in the submitted\n // payload — converting captured strokes to an uploadable image isn't\n // wired up yet. Track this manually if you need it for now.`;
162
+ case 'location':
163
+ return `${col.name}: _${col.name}?.address,`;
164
+ default:
165
+ return `${col.name}: _${col.name},`;
166
+ }
167
+ }
168
+ // FIX: fields the developer marked hidden (col.hidden === true) — e.g.
169
+ // foreign keys like order_id, tenant_id — never get a widget rendered.
170
+ // They still get a state variable + init + submit treatment from the
171
+ // surrounding loops (see generateFormPage below), so the existing value
172
+ // round-trips through an edit unchanged. This is deliberately separate
173
+ // from ui_type: 'hidden' (which renders NxfHidden, a real — if invisible
174
+ // — widget in the tree); col.hidden means "no widget at all."
175
+ function buildVisualLines(col) {
176
+ if (col.hidden)
177
+ return [];
178
+ const info = getWidgetInfo(col);
179
+ const label = col.name.replaceAll('_', ' ');
180
+ const opts = col.options ?? [];
181
+ const optsLiteral = `[${opts.map((o) => `'${o}'`).join(', ')}]`;
182
+ const lines = [];
183
+ switch (info.kind) {
184
+ case 'string':
185
+ lines.push(` ${info.cls}(`);
186
+ lines.push(` label: '${label}',`);
187
+ lines.push(` value: _${col.name},`);
188
+ lines.push(` required: ${col.required ? 'true' : 'false'},`);
189
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
190
+ lines.push(` ),`);
191
+ break;
192
+ case 'string-no-required':
193
+ lines.push(` ${info.cls}(`);
194
+ lines.push(` label: '${label}',`);
195
+ lines.push(` value: _${col.name},`);
196
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
197
+ lines.push(` ),`);
198
+ break;
199
+ case 'string-no-value':
200
+ lines.push(` ${info.cls}(`);
201
+ lines.push(` label: '${label}',`);
202
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
203
+ lines.push(` ),`);
204
+ break;
205
+ case 'string-required':
206
+ lines.push(` ${info.cls}(`);
207
+ lines.push(` label: '${label}',`);
208
+ lines.push(` value: _${col.name},`);
209
+ lines.push(` options: ${info.cls === 'NxfAutocomplete' ? optsLiteral : '<String>[]'},`);
210
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
211
+ lines.push(` ),`);
212
+ break;
213
+ case 'string-select':
214
+ lines.push(` ${info.cls}(`);
215
+ lines.push(` label: '${label}',`);
216
+ lines.push(` value: _${col.name},`);
217
+ lines.push(` options: ${optsLiteral},`);
218
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
219
+ lines.push(` ),`);
220
+ break;
221
+ case 'string-list':
222
+ lines.push(` ${info.cls}(`);
223
+ lines.push(` label: '${label}',`);
224
+ lines.push(` value: _${col.name},`);
225
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
226
+ lines.push(` ),`);
227
+ break;
228
+ case 'string-list-opts':
229
+ lines.push(` ${info.cls}(`);
230
+ lines.push(` label: '${label}',`);
231
+ lines.push(` value: _${col.name},`);
232
+ lines.push(` options: ${optsLiteral},`);
233
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
234
+ lines.push(` ),`);
235
+ break;
236
+ case 'num':
237
+ lines.push(` ${info.cls}(`);
238
+ lines.push(` label: '${label}',`);
239
+ lines.push(` value: _${col.name},`);
240
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
241
+ lines.push(` ),`);
242
+ break;
243
+ case 'double-req':
244
+ lines.push(` ${info.cls}(`);
245
+ lines.push(` label: '${label}',`);
246
+ lines.push(` value: _${col.name},`);
247
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
248
+ lines.push(` ),`);
249
+ break;
250
+ case 'range':
251
+ lines.push(` ${info.cls}(`);
252
+ lines.push(` label: '${label}',`);
253
+ lines.push(` value: _${col.name},`);
254
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
255
+ lines.push(` ),`);
256
+ break;
257
+ case 'int-req':
258
+ lines.push(` ${info.cls}(`);
259
+ lines.push(` label: '${label}',`);
260
+ lines.push(` value: _${col.name},`);
261
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
262
+ lines.push(` ),`);
263
+ break;
264
+ case 'bool':
265
+ lines.push(` ${info.cls}(`);
266
+ lines.push(` label: '${label}',`);
267
+ lines.push(` value: _${col.name},`);
268
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
269
+ lines.push(` ),`);
270
+ break;
271
+ case 'datetime':
272
+ lines.push(` ${info.cls}(`);
273
+ lines.push(` label: '${label}',`);
274
+ lines.push(` value: _${col.name},`);
275
+ lines.push(` required: ${col.required ? 'true' : 'false'},`);
276
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
277
+ lines.push(` ),`);
278
+ break;
279
+ case 'datetime-no-required':
280
+ lines.push(` ${info.cls}(`);
281
+ lines.push(` label: '${label}',`);
282
+ lines.push(` value: _${col.name},`);
283
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
284
+ lines.push(` ),`);
285
+ break;
286
+ case 'time':
287
+ lines.push(` ${info.cls}(`);
288
+ lines.push(` label: '${label}',`);
289
+ lines.push(` value: _${col.name},`);
290
+ lines.push(` required: ${col.required ? 'true' : 'false'},`);
291
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
292
+ lines.push(` ),`);
293
+ break;
294
+ case 'daterange':
295
+ lines.push(` ${info.cls}(`);
296
+ lines.push(` label: '${label}',`);
297
+ lines.push(` value: _${col.name},`);
298
+ lines.push(` required: ${col.required ? 'true' : 'false'},`);
299
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
300
+ lines.push(` ),`);
301
+ break;
302
+ case 'relative-date':
303
+ lines.push(` ${info.cls}(`);
304
+ lines.push(` label: '${label}',`);
305
+ lines.push(` value: _${col.name},`);
306
+ lines.push(` onChanged: (date) => setState(() => _${col.name} = date.difference(DateTime.now()).inDays),`);
307
+ lines.push(` ),`);
308
+ break;
309
+ case 'map':
310
+ lines.push(` ${info.cls}(`);
311
+ lines.push(` label: '${label}',`);
312
+ lines.push(` value: _${col.name},`);
313
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
314
+ lines.push(` ),`);
315
+ break;
316
+ case 'checklist':
317
+ lines.push(` ${info.cls}(`);
318
+ lines.push(` label: '${label}',`);
319
+ lines.push(` value: _${col.name},`);
320
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
321
+ lines.push(` ),`);
322
+ break;
323
+ case 'repeater':
324
+ lines.push(` // NOTE: repeater sub-fields aren't part of the model schema —`);
325
+ lines.push(` // defaulting to a single 'value' column. Adjust fieldKeys below`);
326
+ lines.push(` // to match what each row of ${col.name} should actually contain.`);
327
+ lines.push(` ${info.cls}(`);
328
+ lines.push(` label: '${label}',`);
329
+ lines.push(` value: _${col.name},`);
330
+ lines.push(` fieldKeys: const ['value'],`);
331
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
332
+ lines.push(` ),`);
333
+ break;
334
+ case 'upload-single':
335
+ lines.push(` ${info.cls}(`);
336
+ lines.push(` label: '${label}',`);
337
+ lines.push(` value: _${col.name},`);
338
+ if (info.uploadParam === 'onPickFromLibrary') {
339
+ lines.push(` // Opens the real media-library browser (NxfStorage.list()).`);
340
+ lines.push(` onPickFromLibrary: _pickFromLibrary,`);
341
+ }
342
+ else if (info.uploadParam === 'onPickImage') {
343
+ lines.push(` onPickImage: _pickAndUploadImage,`);
344
+ }
345
+ else if (info.uploadParam === 'onPickFile') {
346
+ lines.push(` onPickFile: () => _pickAndUploadFile(),`);
347
+ }
348
+ else if (info.uploadParam === 'onPickDocument') {
349
+ lines.push(` onPickDocument: () => _pickAndUploadFile(extensions: ['pdf', 'doc', 'docx', 'txt']),`);
350
+ }
351
+ else if (info.uploadParam === 'onPickVideo') {
352
+ lines.push(` onPickVideo: () => _pickAndUploadFile(extensions: ['mp4', 'mov']),`);
353
+ }
354
+ else if (info.uploadParam === 'onPickAudio') {
355
+ lines.push(` onPickAudio: () => _pickAndUploadFile(extensions: ['mp3', 'wav', 'm4a']),`);
356
+ }
357
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
358
+ lines.push(` ),`);
359
+ break;
360
+ case 'upload-multi':
361
+ lines.push(` ${info.cls}(`);
362
+ lines.push(` label: '${label}',`);
363
+ lines.push(` value: _${col.name},`);
364
+ if (info.uploadParam === 'onPickImages') {
365
+ lines.push(` onPickImages: _pickAndUploadImages,`);
366
+ }
367
+ else {
368
+ lines.push(` onPickFiles: () => _pickAndUploadFiles(),`);
369
+ }
370
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
371
+ lines.push(` ),`);
372
+ break;
373
+ case 'location':
374
+ lines.push(` ${info.cls}(`);
375
+ lines.push(` label: '${label}',`);
376
+ lines.push(` value: _${col.name},`);
377
+ lines.push(` // Opens a real Google Maps picker screen (search + tap-to-drop-pin).`);
378
+ lines.push(` // Requires GOOGLE_MAPS_API_KEY in .env AND native platform`);
379
+ lines.push(` // registration this generator does not set up (see README.md).`);
380
+ lines.push(` onPickLocation: _pickLocation,`);
381
+ lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
382
+ lines.push(` ),`);
383
+ break;
384
+ case 'signature':
385
+ lines.push(` // NOTE: signature is captured locally but not yet uploaded/saved —`);
386
+ lines.push(` // see the comment near _submit() for ${col.name}.`);
387
+ lines.push(` ${info.cls}(`);
388
+ lines.push(` label: '${label}',`);
389
+ lines.push(` onChanged: (points) => setState(() => _${col.name} = points),`);
390
+ lines.push(` ),`);
391
+ break;
392
+ case 'hidden':
393
+ lines.push(` ${info.cls}(value: _${col.name}),`);
394
+ break;
395
+ }
396
+ lines.push(` const SizedBox(height: 16),`);
397
+ return lines;
398
+ }
6
399
  function generateFormPage(page, model, stateManager) {
7
400
  const className = (0, generator_core_1.modelToClassName)(model.name);
8
401
  const fileName = (0, generator_core_1.modelToFileName)(model.name);
9
402
  const pk = (0, generator_core_1.getPrimaryKey)(model);
10
403
  const pkName = pk?.name ?? 'id';
11
- const columns = (0, generator_core_1.getVisibleColumns)(model);
404
+ const columns = (0, generator_core_1.getAllNonPrimaryColumns)(model);
12
405
  const pageClass = page.name.replace(/\s+/g, '') + 'FormView';
406
+ const usedImports = new Map();
407
+ for (const col of columns) {
408
+ if (col.hidden)
409
+ continue;
410
+ const info = getWidgetInfo(col);
411
+ usedImports.set(info.cls, `../widgets/${info.category}/edit/${info.file}.dart`);
412
+ }
413
+ const needsUpload = columns.some((c) => !c.hidden && (getWidgetInfo(c).kind === 'upload-single' || getWidgetInfo(c).kind === 'upload-multi'));
414
+ const needsLibrary = columns.some((c) => !c.hidden && getWidgetInfo(c).uploadParam === 'onPickFromLibrary');
415
+ const needsLocation = columns.some((c) => !c.hidden && getWidgetInfo(c).kind === 'location');
13
416
  const lines = [];
14
417
  lines.push(`// GENERATED BY UNDERPEAKS CODEGEN v1.0.0`);
15
418
  lines.push(`// Page: ${page.name} — form`);
@@ -20,8 +423,23 @@ function generateFormPage(page, model, stateManager) {
20
423
  lines.push(``);
21
424
  lines.push(`import 'package:flutter/material.dart';`);
22
425
  lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
23
- lines.push(`import '../../generated/models/${fileName}.g.dart';`);
426
+ if (needsUpload) {
427
+ lines.push(`import 'dart:io';`);
428
+ lines.push(`import 'package:image_picker/image_picker.dart';`);
429
+ lines.push(`import 'package:file_picker/file_picker.dart';`);
430
+ lines.push(`import '../../generated/integrations/storage.g.dart';`);
431
+ }
432
+ if (needsLocation) {
433
+ lines.push(`import 'dart:convert';`);
434
+ lines.push(`import 'package:http/http.dart' as http;`);
435
+ lines.push(`import 'package:google_maps_flutter/google_maps_flutter.dart';`);
436
+ lines.push(`import '../../config/api_config.dart';`);
437
+ }
438
+ lines.push(`import '../../generated/models/${fileName}.dart';`);
24
439
  lines.push(`import '../../generated/providers/${fileName}_provider.g.dart';`);
440
+ for (const [cls, importPath] of usedImports) {
441
+ lines.push(`import '${importPath}';`);
442
+ }
25
443
  lines.push(``);
26
444
  lines.push(`class ${pageClass} extends ConsumerStatefulWidget {`);
27
445
  lines.push(` final ${className}? initial;`);
@@ -36,16 +454,11 @@ function generateFormPage(page, model, stateManager) {
36
454
  lines.push(`class _${pageClass}State extends ConsumerState<${pageClass}> {`);
37
455
  lines.push(` final _formKey = GlobalKey<FormState>();`);
38
456
  lines.push(``);
39
- // Controller declarations
40
457
  for (const col of columns) {
41
- const { dartType } = (0, generator_core_1.parseColumnType)(col);
42
- if (dartType.includes('bool')) {
43
- lines.push(` bool _${col.name} = false;`);
44
- }
45
- else {
46
- lines.push(` final _${col.name}Controller = TextEditingController();`);
47
- }
458
+ const kind = getWidgetInfo(col).kind;
459
+ lines.push(` ${stateType(kind)} _${col.name} = ${defaultExpr(kind)};`);
48
460
  }
461
+ lines.push(...(0, generator_core_2.buildMarkerComment)('//', 'controller').map((l) => ` ${l}`));
49
462
  lines.push(``);
50
463
  lines.push(` bool get _isEditing => widget.initial?.${pkName} != null || widget.id != null;`);
51
464
  lines.push(``);
@@ -54,52 +467,93 @@ function generateFormPage(page, model, stateManager) {
54
467
  lines.push(` super.initState();`);
55
468
  lines.push(` if (widget.initial != null) {`);
56
469
  for (const col of columns) {
57
- const { dartType } = (0, generator_core_1.parseColumnType)(col);
58
- if (dartType.includes('bool')) {
59
- lines.push(` _${col.name} = widget.initial?.${col.name} ?? false;`);
60
- }
61
- else {
62
- lines.push(` _${col.name}Controller.text = widget.initial?.${col.name}?.toString() ?? '';`);
63
- }
470
+ const kind = getWidgetInfo(col).kind;
471
+ lines.push(` _${col.name} = ${buildInitExpr(col, kind)};`);
64
472
  }
473
+ lines.push(...(0, generator_core_2.buildMarkerComment)('//', 'init').map((l) => ` ${l}`));
65
474
  lines.push(` }`);
66
475
  lines.push(` }`);
67
476
  lines.push(``);
68
477
  lines.push(` @override`);
69
478
  lines.push(` void dispose() {`);
70
- for (const col of columns) {
71
- const { dartType } = (0, generator_core_1.parseColumnType)(col);
72
- if (!dartType.includes('bool')) {
73
- lines.push(` _${col.name}Controller.dispose();`);
74
- }
75
- }
479
+ lines.push(...(0, generator_core_2.buildMarkerComment)('//', 'dispose').map((l) => ` ${l}`));
76
480
  lines.push(` super.dispose();`);
77
481
  lines.push(` }`);
78
482
  lines.push(``);
483
+ if (needsUpload) {
484
+ lines.push(` Future<String?> _pickAndUploadImage() async {`);
485
+ lines.push(` final picked = await ImagePicker().pickImage(source: ImageSource.gallery);`);
486
+ lines.push(` if (picked == null) return null;`);
487
+ lines.push(` final uploaded = await NxfStorage.upload(File(picked.path), folder: 'uploads');`);
488
+ lines.push(` return uploaded.url;`);
489
+ lines.push(` }`);
490
+ lines.push(``);
491
+ lines.push(` Future<List<String>> _pickAndUploadImages() async {`);
492
+ lines.push(` final picked = await ImagePicker().pickMultiImage();`);
493
+ lines.push(` final urls = <String>[];`);
494
+ lines.push(` for (final file in picked) {`);
495
+ lines.push(` final uploaded = await NxfStorage.upload(File(file.path), folder: 'uploads');`);
496
+ lines.push(` urls.add(uploaded.url);`);
497
+ lines.push(` }`);
498
+ lines.push(` return urls;`);
499
+ lines.push(` }`);
500
+ lines.push(``);
501
+ lines.push(` Future<String?> _pickAndUploadFile({List<String>? extensions}) async {`);
502
+ lines.push(` final result = await FilePicker.platform.pickFiles(`);
503
+ lines.push(` type: extensions != null ? FileType.custom : FileType.any,`);
504
+ lines.push(` allowedExtensions: extensions,`);
505
+ lines.push(` );`);
506
+ lines.push(` final path = result?.files.single.path;`);
507
+ lines.push(` if (path == null) return null;`);
508
+ lines.push(` final uploaded = await NxfStorage.upload(File(path), folder: 'uploads');`);
509
+ lines.push(` return uploaded.url;`);
510
+ lines.push(` }`);
511
+ lines.push(``);
512
+ lines.push(` Future<List<String>> _pickAndUploadFiles() async {`);
513
+ lines.push(` final result = await FilePicker.platform.pickFiles(allowMultiple: true);`);
514
+ lines.push(` final urls = <String>[];`);
515
+ lines.push(` for (final file in result?.files ?? []) {`);
516
+ lines.push(` if (file.path == null) continue;`);
517
+ lines.push(` final uploaded = await NxfStorage.upload(File(file.path!), folder: 'uploads');`);
518
+ lines.push(` urls.add(uploaded.url);`);
519
+ lines.push(` }`);
520
+ lines.push(` return urls;`);
521
+ lines.push(` }`);
522
+ lines.push(``);
523
+ }
524
+ if (needsLibrary) {
525
+ lines.push(` // Opens a real browser of previously-uploaded files (NxfStorage.list())`);
526
+ lines.push(` // instead of falling back to a fresh device pick.`);
527
+ lines.push(` Future<String?> _pickFromLibrary() async {`);
528
+ lines.push(` return Navigator.of(context).push<String>(`);
529
+ lines.push(` MaterialPageRoute(builder: (_) => const _MediaLibraryPickerScreen()),`);
530
+ lines.push(` );`);
531
+ lines.push(` }`);
532
+ lines.push(``);
533
+ }
534
+ if (needsLocation) {
535
+ lines.push(` // Opens a real Google Maps picker screen (search + tap-to-drop-pin),`);
536
+ lines.push(` // backed by GOOGLE_MAPS_API_KEY. Native platform registration for that`);
537
+ lines.push(` // key (AndroidManifest.xml / AppDelegate.swift) is NOT set up by this`);
538
+ lines.push(` // generator — see README.md.`);
539
+ lines.push(` Future<NxfGeoValue?> _pickLocation() async {`);
540
+ lines.push(` return Navigator.of(context).push<NxfGeoValue>(`);
541
+ lines.push(` MaterialPageRoute(`);
542
+ lines.push(` builder: (_) => _LocationPickerScreen(apiKey: ApiConfig.googleMapsApiKey),`);
543
+ lines.push(` ),`);
544
+ lines.push(` );`);
545
+ lines.push(` }`);
546
+ lines.push(``);
547
+ }
79
548
  lines.push(` Future<void> _submit() async {`);
80
549
  lines.push(` if (!_formKey.currentState!.validate()) return;`);
81
550
  lines.push(``);
82
551
  lines.push(` final item = ${className}(`);
83
552
  for (const col of columns) {
84
- const { dartType } = (0, generator_core_1.parseColumnType)(col);
85
- if (dartType.includes('bool')) {
86
- lines.push(` ${col.name}: _${col.name},`);
87
- }
88
- else if (dartType.includes('int')) {
89
- lines.push(` ${col.name}: int.tryParse(_${col.name}Controller.text.trim()),`);
90
- }
91
- else if (dartType.includes('double')) {
92
- lines.push(` ${col.name}: double.tryParse(_${col.name}Controller.text.trim()),`);
93
- }
94
- else if (dartType.includes('DateTime')) {
95
- lines.push(` ${col.name}: DateTime.tryParse(_${col.name}Controller.text.trim()),`);
96
- }
97
- else {
98
- lines.push(` ${col.name}: _${col.name}Controller.text.trim().isEmpty`);
99
- lines.push(` ? null`);
100
- lines.push(` : _${col.name}Controller.text.trim(),`);
101
- }
553
+ const kind = getWidgetInfo(col).kind;
554
+ lines.push(` ${buildSubmitExpr(col, kind)}`);
102
555
  }
556
+ lines.push(...(0, generator_core_2.buildMarkerComment)('//', 'submit').map((l) => ` ${l}`));
103
557
  lines.push(` );`);
104
558
  lines.push(``);
105
559
  lines.push(` if (_isEditing) {`);
@@ -143,39 +597,10 @@ function generateFormPage(page, model, stateManager) {
143
597
  lines.push(` child: Column(`);
144
598
  lines.push(` crossAxisAlignment: CrossAxisAlignment.stretch,`);
145
599
  lines.push(` children: [`);
146
- // Form fields
147
600
  for (const col of columns) {
148
- const { dartType } = (0, generator_core_1.parseColumnType)(col);
149
- const label = col.name.replaceAll('_', ' ');
150
- if (dartType.includes('bool')) {
151
- lines.push(` SwitchListTile(`);
152
- lines.push(` title: const Text('${label}'),`);
153
- lines.push(` value: _${col.name},`);
154
- lines.push(` onChanged: (v) => setState(() => _${col.name} = v),`);
155
- lines.push(` ),`);
156
- }
157
- else {
158
- const keyboard = dartType.includes('int') || dartType.includes('double')
159
- ? 'TextInputType.number'
160
- : dartType.includes('DateTime')
161
- ? 'TextInputType.datetime'
162
- : 'TextInputType.text';
163
- lines.push(` TextFormField(`);
164
- lines.push(` controller: _${col.name}Controller,`);
165
- lines.push(` keyboardType: ${keyboard},`);
166
- lines.push(` decoration: const InputDecoration(`);
167
- lines.push(` labelText: '${label}',`);
168
- lines.push(` border: OutlineInputBorder(),`);
169
- lines.push(` ),`);
170
- if (col.required) {
171
- lines.push(` validator: (v) => v == null || v.trim().isEmpty`);
172
- lines.push(` ? '${label} is required'`);
173
- lines.push(` : null,`);
174
- }
175
- lines.push(` ),`);
176
- }
177
- lines.push(` const SizedBox(height: 16),`);
601
+ lines.push(...buildVisualLines(col));
178
602
  }
603
+ lines.push(...(0, generator_core_2.buildMarkerComment)('//', 'visual').map((l) => ` ${l}`));
179
604
  lines.push(` FilledButton(`);
180
605
  lines.push(` onPressed: loading ? null : _submit,`);
181
606
  lines.push(` child: loading`);
@@ -196,10 +621,292 @@ function generateFormPage(page, model, stateManager) {
196
621
  lines.push(` );`);
197
622
  lines.push(` }`);
198
623
  lines.push(`}`);
624
+ if (needsLibrary) {
625
+ lines.push(``);
626
+ lines.push(`// ── Media library picker ────────────────────────────────────────────────`);
627
+ lines.push(``);
628
+ lines.push(`class _MediaLibraryPickerScreen extends StatefulWidget {`);
629
+ lines.push(` const _MediaLibraryPickerScreen();`);
630
+ lines.push(``);
631
+ lines.push(` @override`);
632
+ lines.push(` State<_MediaLibraryPickerScreen> createState() => _MediaLibraryPickerScreenState();`);
633
+ lines.push(`}`);
634
+ lines.push(``);
635
+ lines.push(`class _MediaLibraryPickerScreenState extends State<_MediaLibraryPickerScreen> {`);
636
+ lines.push(` List<NxfStorageFile>? _files;`);
637
+ lines.push(` String? _error;`);
638
+ lines.push(``);
639
+ lines.push(` @override`);
640
+ lines.push(` void initState() {`);
641
+ lines.push(` super.initState();`);
642
+ lines.push(` _load();`);
643
+ lines.push(` }`);
644
+ lines.push(``);
645
+ lines.push(` Future<void> _load() async {`);
646
+ lines.push(` try {`);
647
+ lines.push(` final files = await NxfStorage.list();`);
648
+ lines.push(` if (mounted) setState(() => _files = files);`);
649
+ lines.push(` } catch (e) {`);
650
+ lines.push(` if (mounted) setState(() => _error = 'Could not load media library: \$e');`);
651
+ lines.push(` }`);
652
+ lines.push(` }`);
653
+ lines.push(``);
654
+ lines.push(` bool _isImage(String fileName) {`);
655
+ lines.push(` final lower = fileName.toLowerCase();`);
656
+ lines.push(` return lower.endsWith('.png') || lower.endsWith('.jpg') ||`);
657
+ lines.push(` lower.endsWith('.jpeg') || lower.endsWith('.gif') ||`);
658
+ lines.push(` lower.endsWith('.webp');`);
659
+ lines.push(` }`);
660
+ lines.push(``);
661
+ lines.push(` @override`);
662
+ lines.push(` Widget build(BuildContext context) {`);
663
+ lines.push(` return Scaffold(`);
664
+ lines.push(` appBar: AppBar(title: const Text('Media Library')),`);
665
+ lines.push(` body: _error != null`);
666
+ lines.push(` ? Center(child: Padding(padding: const EdgeInsets.all(24), child: Text(_error!, style: const TextStyle(color: Colors.red))))`);
667
+ lines.push(` : _files == null`);
668
+ lines.push(` ? const Center(child: CircularProgressIndicator())`);
669
+ lines.push(` : _files!.isEmpty`);
670
+ lines.push(` ? const Center(child: Text('No files uploaded yet.', style: TextStyle(color: Colors.grey)))`);
671
+ lines.push(` : GridView.builder(`);
672
+ lines.push(` padding: const EdgeInsets.all(12),`);
673
+ lines.push(` gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(`);
674
+ lines.push(` crossAxisCount: 3,`);
675
+ lines.push(` crossAxisSpacing: 8,`);
676
+ lines.push(` mainAxisSpacing: 8,`);
677
+ lines.push(` ),`);
678
+ lines.push(` itemCount: _files!.length,`);
679
+ lines.push(` itemBuilder: (context, index) {`);
680
+ lines.push(` final file = _files![index];`);
681
+ lines.push(` return InkWell(`);
682
+ lines.push(` onTap: () => Navigator.of(context).pop(file.url),`);
683
+ lines.push(` child: ClipRRect(`);
684
+ lines.push(` borderRadius: BorderRadius.circular(8),`);
685
+ lines.push(` child: _isImage(file.fileName)`);
686
+ lines.push(` ? Image.network(`);
687
+ lines.push(` file.url,`);
688
+ lines.push(` fit: BoxFit.cover,`);
689
+ lines.push(` errorBuilder: (_, __, ___) => Container(`);
690
+ lines.push(` color: Colors.grey.shade200,`);
691
+ lines.push(` child: const Icon(Icons.broken_image_outlined, color: Colors.grey),`);
692
+ lines.push(` ),`);
693
+ lines.push(` )`);
694
+ lines.push(` : Container(`);
695
+ lines.push(` color: Colors.grey.shade100,`);
696
+ lines.push(` padding: const EdgeInsets.all(4),`);
697
+ lines.push(` child: Column(`);
698
+ lines.push(` mainAxisAlignment: MainAxisAlignment.center,`);
699
+ lines.push(` children: [`);
700
+ lines.push(` const Icon(Icons.insert_drive_file_outlined, color: Colors.grey),`);
701
+ lines.push(` const SizedBox(height: 4),`);
702
+ lines.push(` Text(`);
703
+ lines.push(` file.fileName,`);
704
+ lines.push(` style: const TextStyle(fontSize: 10),`);
705
+ lines.push(` maxLines: 2,`);
706
+ lines.push(` overflow: TextOverflow.ellipsis,`);
707
+ lines.push(` textAlign: TextAlign.center,`);
708
+ lines.push(` ),`);
709
+ lines.push(` ],`);
710
+ lines.push(` ),`);
711
+ lines.push(` ),`);
712
+ lines.push(` ),`);
713
+ lines.push(` );`);
714
+ lines.push(` },`);
715
+ lines.push(` ),`);
716
+ lines.push(` );`);
717
+ lines.push(` }`);
718
+ lines.push(`}`);
719
+ }
720
+ if (needsLocation) {
721
+ lines.push(``);
722
+ lines.push(`// ── Location picker (real Google Maps) ──────────────────────────────────`);
723
+ lines.push(``);
724
+ lines.push(`class _LocationPickerScreen extends StatefulWidget {`);
725
+ lines.push(` final String apiKey;`);
726
+ lines.push(` const _LocationPickerScreen({required this.apiKey});`);
727
+ lines.push(``);
728
+ lines.push(` @override`);
729
+ lines.push(` State<_LocationPickerScreen> createState() => _LocationPickerScreenState();`);
730
+ lines.push(`}`);
731
+ lines.push(``);
732
+ lines.push(`class _LocationPickerScreenState extends State<_LocationPickerScreen> {`);
733
+ lines.push(` final _searchCtrl = TextEditingController();`);
734
+ lines.push(` GoogleMapController? _mapController;`);
735
+ lines.push(` LatLng _center = const LatLng(0, 0);`);
736
+ lines.push(` Marker? _marker;`);
737
+ lines.push(` String? _address;`);
738
+ lines.push(` List<Map<String, String>> _suggestions = [];`);
739
+ lines.push(` bool _searching = false;`);
740
+ lines.push(``);
741
+ lines.push(` @override`);
742
+ lines.push(` void dispose() {`);
743
+ lines.push(` _searchCtrl.dispose();`);
744
+ lines.push(` super.dispose();`);
745
+ lines.push(` }`);
746
+ lines.push(``);
747
+ lines.push(` Future<void> _search(String query) async {`);
748
+ lines.push(` if (query.trim().isEmpty) {`);
749
+ lines.push(` setState(() => _suggestions = []);`);
750
+ lines.push(` return;`);
751
+ lines.push(` }`);
752
+ lines.push(` setState(() => _searching = true);`);
753
+ lines.push(` try {`);
754
+ lines.push(` final uri = Uri.parse(`);
755
+ lines.push(` 'https://maps.googleapis.com/maps/api/place/autocomplete/json'`);
756
+ lines.push(` '?input=\${Uri.encodeComponent(query)}&key=\${widget.apiKey}',`);
757
+ lines.push(` );`);
758
+ lines.push(` final res = await http.get(uri);`);
759
+ lines.push(` final json = jsonDecode(res.body) as Map<String, dynamic>;`);
760
+ lines.push(` final predictions = (json['predictions'] as List<dynamic>? ?? []);`);
761
+ lines.push(` setState(() {`);
762
+ lines.push(` _suggestions = predictions.map((p) => {`);
763
+ lines.push(` 'place_id': p['place_id'].toString(),`);
764
+ lines.push(` 'description': p['description'].toString(),`);
765
+ lines.push(` }).toList();`);
766
+ lines.push(` });`);
767
+ lines.push(` } catch (_) {`);
768
+ lines.push(` // Silently ignore — user can still drop a pin manually.`);
769
+ lines.push(` } finally {`);
770
+ lines.push(` if (mounted) setState(() => _searching = false);`);
771
+ lines.push(` }`);
772
+ lines.push(` }`);
773
+ lines.push(``);
774
+ lines.push(` Future<void> _selectPrediction(String placeId, String description) async {`);
775
+ lines.push(` try {`);
776
+ lines.push(` final uri = Uri.parse(`);
777
+ lines.push(` 'https://maps.googleapis.com/maps/api/place/details/json'`);
778
+ lines.push(` '?place_id=\$placeId&key=\${widget.apiKey}',`);
779
+ lines.push(` );`);
780
+ lines.push(` final res = await http.get(uri);`);
781
+ lines.push(` final json = jsonDecode(res.body) as Map<String, dynamic>;`);
782
+ lines.push(` final loc = json['result']?['geometry']?['location'] as Map<String, dynamic>?;`);
783
+ lines.push(` if (loc == null) return;`);
784
+ lines.push(` final lat = (loc['lat'] as num).toDouble();`);
785
+ lines.push(` final lng = (loc['lng'] as num).toDouble();`);
786
+ lines.push(` _setPin(LatLng(lat, lng), description);`);
787
+ lines.push(` _mapController?.animateCamera(CameraUpdate.newLatLngZoom(LatLng(lat, lng), 16));`);
788
+ lines.push(` } catch (_) {`);
789
+ lines.push(` // Ignore — user can still tap the map directly.`);
790
+ lines.push(` }`);
791
+ lines.push(` setState(() => _suggestions = []);`);
792
+ lines.push(` }`);
793
+ lines.push(``);
794
+ lines.push(` Future<void> _onMapTap(LatLng point) async {`);
795
+ lines.push(` _setPin(point, null);`);
796
+ lines.push(` try {`);
797
+ lines.push(` final uri = Uri.parse(`);
798
+ lines.push(` 'https://maps.googleapis.com/maps/api/geocode/json'`);
799
+ lines.push(` '?latlng=\${point.latitude},\${point.longitude}&key=\${widget.apiKey}',`);
800
+ lines.push(` );`);
801
+ lines.push(` final res = await http.get(uri);`);
802
+ lines.push(` final json = jsonDecode(res.body) as Map<String, dynamic>;`);
803
+ lines.push(` final results = json['results'] as List<dynamic>? ?? [];`);
804
+ lines.push(` final address = results.isNotEmpty ? results.first['formatted_address']?.toString() : null;`);
805
+ lines.push(` if (address != null) setState(() => _address = address);`);
806
+ lines.push(` } catch (_) {`);
807
+ lines.push(` // Address lookup failed — lat/lng are still usable without a label.`);
808
+ lines.push(` }`);
809
+ lines.push(` }`);
810
+ lines.push(``);
811
+ lines.push(` void _setPin(LatLng point, String? address) {`);
812
+ lines.push(` setState(() {`);
813
+ lines.push(` _center = point;`);
814
+ lines.push(` _marker = Marker(markerId: const MarkerId('picked'), position: point);`);
815
+ lines.push(` if (address != null) _address = address;`);
816
+ lines.push(` });`);
817
+ lines.push(` }`);
818
+ lines.push(``);
819
+ lines.push(` @override`);
820
+ lines.push(` Widget build(BuildContext context) {`);
821
+ lines.push(` return Scaffold(`);
822
+ lines.push(` appBar: AppBar(`);
823
+ lines.push(` title: const Text('Pick a location'),`);
824
+ lines.push(` actions: [`);
825
+ lines.push(` if (_marker != null)`);
826
+ lines.push(` TextButton(`);
827
+ lines.push(` onPressed: () => Navigator.of(context).pop(`);
828
+ lines.push(` NxfGeoValue(`);
829
+ lines.push(` lat: _marker!.position.latitude,`);
830
+ lines.push(` lng: _marker!.position.longitude,`);
831
+ lines.push(` address: _address ?? '\${_marker!.position.latitude}, \${_marker!.position.longitude}',`);
832
+ lines.push(` ),`);
833
+ lines.push(` ),`);
834
+ lines.push(` child: const Text('Save', style: TextStyle(color: Colors.white)),`);
835
+ lines.push(` ),`);
836
+ lines.push(` ],`);
837
+ lines.push(` ),`);
838
+ lines.push(` body: Stack(`);
839
+ lines.push(` children: [`);
840
+ lines.push(` GoogleMap(`);
841
+ lines.push(` initialCameraPosition: CameraPosition(target: _center, zoom: 2),`);
842
+ lines.push(` onMapCreated: (c) => _mapController = c,`);
843
+ lines.push(` onTap: _onMapTap,`);
844
+ lines.push(` markers: _marker != null ? {_marker!} : {},`);
845
+ lines.push(` ),`);
846
+ lines.push(` Positioned(`);
847
+ lines.push(` top: 12, left: 12, right: 12,`);
848
+ lines.push(` child: Column(`);
849
+ lines.push(` children: [`);
850
+ lines.push(` Material(`);
851
+ lines.push(` elevation: 4,`);
852
+ lines.push(` borderRadius: BorderRadius.circular(12),`);
853
+ lines.push(` child: TextField(`);
854
+ lines.push(` controller: _searchCtrl,`);
855
+ lines.push(` decoration: InputDecoration(`);
856
+ lines.push(` hintText: 'Search for an address...',`);
857
+ lines.push(` prefixIcon: const Icon(Icons.search),`);
858
+ lines.push(` suffixIcon: _searching`);
859
+ lines.push(` ? const Padding(`);
860
+ lines.push(` padding: EdgeInsets.all(12),`);
861
+ lines.push(` child: SizedBox(`);
862
+ lines.push(` height: 16, width: 16,`);
863
+ lines.push(` child: CircularProgressIndicator(strokeWidth: 2),`);
864
+ lines.push(` ),`);
865
+ lines.push(` )`);
866
+ lines.push(` : null,`);
867
+ lines.push(` border: InputBorder.none,`);
868
+ lines.push(` contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),`);
869
+ lines.push(` ),`);
870
+ lines.push(` onChanged: _search,`);
871
+ lines.push(` ),`);
872
+ lines.push(` ),`);
873
+ lines.push(` if (_suggestions.isNotEmpty)`);
874
+ lines.push(` Material(`);
875
+ lines.push(` elevation: 4,`);
876
+ lines.push(` borderRadius: BorderRadius.circular(12),`);
877
+ lines.push(` child: Column(`);
878
+ lines.push(` children: _suggestions.map((s) => ListTile(`);
879
+ lines.push(` title: Text(s['description'] ?? ''),`);
880
+ lines.push(` onTap: () => _selectPrediction(s['place_id']!, s['description']!),`);
881
+ lines.push(` )).toList(),`);
882
+ lines.push(` ),`);
883
+ lines.push(` ),`);
884
+ lines.push(` ],`);
885
+ lines.push(` ),`);
886
+ lines.push(` ),`);
887
+ lines.push(` ],`);
888
+ lines.push(` ),`);
889
+ lines.push(` );`);
890
+ lines.push(` }`);
891
+ lines.push(`}`);
892
+ }
893
+ const fieldRenders = {};
894
+ for (const col of columns) {
895
+ const kind = getWidgetInfo(col).kind;
896
+ fieldRenders[col.name] = {
897
+ controller: [` ${stateType(kind)} _${col.name} = ${defaultExpr(kind)};`],
898
+ init: [` _${col.name} = ${buildInitExpr(col, kind)};`],
899
+ dispose: [],
900
+ submit: [` ${buildSubmitExpr(col, kind)}`],
901
+ visual: buildVisualLines(col),
902
+ };
903
+ }
199
904
  return {
200
905
  path: `lib/presentation/views/${fileName}_form_view.dart`,
201
906
  content: lines.join('\n'),
202
907
  category: 'customizable',
908
+ field_names: columns.map((c) => c.name),
909
+ field_renders: fieldRenders,
203
910
  };
204
911
  }
205
912
  //# sourceMappingURL=page-form.js.map