Skip to content

Commit 332bcce

Browse files
authored
Merge pull request #9 from alexdivadi/feature/firebase-deployment
feat: address fixes, about page, event config enhancement
2 parents a7766ea + d8cb141 commit 332bcce

58 files changed

Lines changed: 2246 additions & 423 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
23.3 KB
Loading

lib/core/app.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class AnyStepApp extends ConsumerWidget {
3939
highContrastDarkTheme: AnyStepTheme.highContrastDarkTheme,
4040
themeMode: themeMode.hasValue ? themeMode.value : ThemeMode.system,
4141
locale: localeAsync.hasValue ? localeAsync.value : null,
42-
builder:
43-
(context, child) => AppStartupWidget(onLoaded: (context) => child ?? const SizedBox()),
42+
builder: (context, child) =>
43+
AppStartupWidget(onLoaded: (context) => child ?? const SizedBox()),
4444
);
4545
}
4646
}

lib/core/common/widgets/dropdown_section.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'package:anystep/core/common/constants/spacing.dart';
22
import 'package:flutter/material.dart';
33

4-
class DropdownSection extends StatefulWidget {
5-
const DropdownSection({
4+
class DropdownText extends StatefulWidget {
5+
const DropdownText({
66
super.key,
77
required this.title,
88
required this.content,
@@ -16,10 +16,10 @@ class DropdownSection extends StatefulWidget {
1616
final EdgeInsetsGeometry padding;
1717

1818
@override
19-
State<DropdownSection> createState() => _DropdownSectionState();
19+
State<DropdownText> createState() => _DropdownTextState();
2020
}
2121

22-
class _DropdownSectionState extends State<DropdownSection> {
22+
class _DropdownTextState extends State<DropdownText> {
2323
bool _expanded = false;
2424

2525
@override
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import 'dart:async';
2+
3+
import 'package:anystep/core/common/constants/spacing.dart';
4+
import 'package:anystep/core/common/utils/log_utils.dart';
5+
import 'package:anystep/core/common/widgets/inputs/any_step_text_field.dart';
6+
import 'package:anystep/core/features/location/data/places_api_client.dart';
7+
import 'package:anystep/core/features/location/domain/places_models.dart';
8+
import 'package:anystep/core/features/location/utils/place_to_address.dart';
9+
import 'package:anystep/l10n/generated/app_localizations.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter_form_builder/flutter_form_builder.dart';
12+
import 'package:flutter_riverpod/flutter_riverpod.dart';
13+
14+
class AddressAutocompleteField extends ConsumerStatefulWidget {
15+
const AddressAutocompleteField({
16+
super.key,
17+
required this.formKey,
18+
this.countryCode = 'US',
19+
this.enabled = true,
20+
});
21+
22+
final GlobalKey<FormBuilderState> formKey;
23+
final String countryCode;
24+
final bool enabled;
25+
26+
@override
27+
ConsumerState<AddressAutocompleteField> createState() => _AddressAutocompleteFieldState();
28+
}
29+
30+
class _AddressAutocompleteFieldState extends ConsumerState<AddressAutocompleteField> {
31+
final TextEditingController _controller = TextEditingController();
32+
final FocusNode _focusNode = FocusNode();
33+
Timer? _debounce;
34+
List<PlacesPrediction> _predictions = [];
35+
bool _isLoading = false;
36+
String? _error;
37+
38+
@override
39+
void initState() {
40+
super.initState();
41+
_focusNode.addListener(_handleFocusChange);
42+
}
43+
44+
@override
45+
void dispose() {
46+
_debounce?.cancel();
47+
_controller.dispose();
48+
_focusNode.removeListener(_handleFocusChange);
49+
_focusNode.dispose();
50+
super.dispose();
51+
}
52+
53+
void _handleFocusChange() {
54+
if (!_focusNode.hasFocus) {
55+
setState(() {
56+
_predictions = [];
57+
_error = null;
58+
});
59+
}
60+
}
61+
62+
void _onChanged(String? value) {
63+
_debounce?.cancel();
64+
final query = value?.trim() ?? '';
65+
if (query.isEmpty) {
66+
setState(() {
67+
_predictions = [];
68+
_error = null;
69+
});
70+
return;
71+
}
72+
_debounce = Timer(const Duration(milliseconds: 300), () async {
73+
setState(() {
74+
_isLoading = true;
75+
_error = null;
76+
});
77+
try {
78+
final results = await ref
79+
.read(placesApiClientProvider)
80+
.autocomplete(query, countryCode: widget.countryCode);
81+
if (!mounted) return;
82+
setState(() {
83+
_predictions = results;
84+
_isLoading = false;
85+
});
86+
} catch (e) {
87+
if (!mounted) return;
88+
setState(() {
89+
_predictions = [];
90+
_isLoading = false;
91+
_error = e.toString();
92+
});
93+
}
94+
});
95+
}
96+
97+
Future<void> _selectPrediction(PlacesPrediction prediction) async {
98+
setState(() {
99+
_isLoading = true;
100+
_error = null;
101+
});
102+
try {
103+
final details = await ref.read(placesApiClientProvider).placeDetails(prediction.placeId);
104+
final parsed = placeDetailsToAddress(details);
105+
final form = widget.formKey.currentState;
106+
if (form == null) return;
107+
form.fields['street']?.didChange(parsed.street);
108+
form.fields['streetSecondary']?.didChange(parsed.streetSecondary);
109+
form.fields['city']?.didChange(parsed.city);
110+
form.fields['state']?.didChange(parsed.state);
111+
form.fields['postalCode']?.didChange(parsed.postalCode);
112+
form.fields['zipCode']?.didChange(parsed.postalCode);
113+
form.fields['placeId']?.didChange(parsed.placeId);
114+
form.fields['latitude']?.didChange(parsed.latitude);
115+
form.fields['longitude']?.didChange(parsed.longitude);
116+
form.fields['placeName']?.didChange(parsed.name ?? prediction.description);
117+
_controller.text = prediction.description;
118+
_controller.selection =
119+
TextSelection.fromPosition(TextPosition(offset: _controller.text.length));
120+
_focusNode.unfocus();
121+
setState(() {
122+
_predictions = [];
123+
_isLoading = false;
124+
});
125+
} catch (e, stackTrace) {
126+
Log.e('Places selection error', e, stackTrace);
127+
if (!mounted) return;
128+
setState(() {
129+
_isLoading = false;
130+
_error = e.toString();
131+
});
132+
}
133+
}
134+
135+
@override
136+
Widget build(BuildContext context) {
137+
final loc = AppLocalizations.of(context);
138+
return Column(
139+
crossAxisAlignment: CrossAxisAlignment.start,
140+
children: [
141+
AnyStepTextField(
142+
name: 'addressSearch',
143+
labelText: loc.searchAddress,
144+
hintText: loc.startTypingAddress,
145+
controller: _controller,
146+
focusNode: _focusNode,
147+
enabled: widget.enabled,
148+
onChanged: _onChanged,
149+
textInputAction: TextInputAction.search,
150+
),
151+
if (_isLoading)
152+
Padding(
153+
padding: const EdgeInsets.only(bottom: AnyStepSpacing.sm4),
154+
child: LinearProgressIndicator(minHeight: 2),
155+
),
156+
if (_error != null)
157+
Padding(
158+
padding: const EdgeInsets.only(bottom: AnyStepSpacing.sm4),
159+
child: Text(
160+
_error!,
161+
style: TextStyle(color: Theme.of(context).colorScheme.error),
162+
),
163+
),
164+
if (_predictions.isEmpty && !_isLoading && _controller.text.trim().isNotEmpty)
165+
Padding(
166+
padding: const EdgeInsets.only(bottom: AnyStepSpacing.sm4),
167+
child: Text(loc.noMatchesFound),
168+
),
169+
if (_predictions.isNotEmpty)
170+
Card(
171+
margin: const EdgeInsets.only(bottom: AnyStepSpacing.sm8),
172+
child: ListView.separated(
173+
shrinkWrap: true,
174+
physics: const NeverScrollableScrollPhysics(),
175+
itemCount: _predictions.length,
176+
separatorBuilder: (_, __) => const Divider(height: 1),
177+
itemBuilder: (context, index) {
178+
final prediction = _predictions[index];
179+
return ListTile(
180+
title: Text(prediction.mainText ?? prediction.description),
181+
subtitle:
182+
prediction.secondaryText != null ? Text(prediction.secondaryText!) : null,
183+
onTap: () => _selectPrediction(prediction),
184+
);
185+
},
186+
),
187+
),
188+
],
189+
);
190+
}
191+
}

0 commit comments

Comments
 (0)