Skip to content

Commit 647ceec

Browse files
committed
add address picker
1 parent ee89e13 commit 647ceec

File tree

7 files changed

+334
-55
lines changed

7 files changed

+334
-55
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import 'package:e_commerce_app/configs/config.dart';
2+
import 'package:e_commerce_app/data/models/delivery_address_model.dart';
3+
import 'package:e_commerce_app/data/models/location_model.dart';
4+
import 'package:e_commerce_app/presentation/screens/delivery_address/address_picker/bloc/bloc.dart';
5+
import 'package:e_commerce_app/utils/utils.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_bloc/flutter_bloc.dart';
8+
9+
class AddressPicker extends StatefulWidget {
10+
final Function(LocationModel, LocationModel, LocationModel) addressChanged;
11+
final DeliveryAddressModel? deliveryAddress;
12+
const AddressPicker({
13+
Key? key,
14+
this.deliveryAddress,
15+
required this.addressChanged,
16+
}) : super(key: key);
17+
18+
@override
19+
_AddressPickerState createState() => _AddressPickerState();
20+
}
21+
22+
class _AddressPickerState extends State<AddressPicker> {
23+
LocationModel? selectedCity;
24+
LocationModel? selectedDistrict;
25+
LocationModel? selectedWard;
26+
27+
@override
28+
void initState() {
29+
if (widget.deliveryAddress != null) {
30+
var detailAddress = widget.deliveryAddress!;
31+
selectedCity = detailAddress.city;
32+
selectedDistrict = detailAddress.district;
33+
selectedWard = detailAddress.ward;
34+
}
35+
super.initState();
36+
}
37+
38+
void onCityChanged(BuildContext context, LocationModel city) {
39+
selectedCity = city;
40+
selectedDistrict = null;
41+
selectedWard = null;
42+
43+
BlocProvider.of<AddressPickerBloc>(context).add(LoadDistricts(city.id));
44+
}
45+
46+
void onDistrictChanged(BuildContext context, LocationModel district) {
47+
selectedDistrict = district;
48+
selectedWard = null;
49+
50+
BlocProvider.of<AddressPickerBloc>(context).add(LoadWards(district.id));
51+
}
52+
53+
void onWardChanged(LocationModel ward) {
54+
setState(() {
55+
selectedWard = ward;
56+
});
57+
widget.addressChanged(selectedCity!, selectedDistrict!, selectedWard!);
58+
}
59+
60+
getDropdownItems(List<LocationModel> list) {
61+
return list
62+
.map((item) => DropdownMenuItem(child: Text(item.name), value: item))
63+
.toList();
64+
}
65+
66+
@override
67+
Widget build(BuildContext context) {
68+
return BlocProvider(
69+
create: (context) {
70+
if (widget.deliveryAddress != null) {
71+
return AddressPickerBloc()
72+
..add(InitialEvent(
73+
cityId: selectedCity!.id,
74+
districtId: selectedDistrict!.id,
75+
));
76+
} else {
77+
return AddressPickerBloc()..add(InitialEvent());
78+
}
79+
},
80+
child: Builder(
81+
builder: (context) {
82+
return BlocBuilder<AddressPickerBloc, AddressPickerState>(
83+
builder: (context, state) {
84+
return Column(
85+
children: [
86+
buildCityPicker(context, state),
87+
SizedBox(height: SizeConfig.defaultSize),
88+
buildDistrictPicker(context, state),
89+
SizedBox(height: SizeConfig.defaultSize),
90+
buildWardPicker(context, state),
91+
],
92+
);
93+
},
94+
);
95+
},
96+
),
97+
);
98+
}
99+
100+
buildCityPicker(BuildContext context, AddressPickerState state) {
101+
return DropdownButtonFormField<LocationModel>(
102+
decoration: InputDecoration(
103+
labelText: Translate.of(context).translate("city"),
104+
),
105+
onChanged: (city) => onCityChanged(context, city!),
106+
items: getDropdownItems(state.cities),
107+
value: state.cities.isEmpty ? null : selectedCity,
108+
);
109+
}
110+
111+
buildDistrictPicker(BuildContext context, AddressPickerState state) {
112+
return DropdownButtonFormField<LocationModel>(
113+
decoration: InputDecoration(
114+
labelText: Translate.of(context).translate("district"),
115+
),
116+
onChanged: (district) => onDistrictChanged(context, district!),
117+
items: getDropdownItems(state.districts),
118+
value: state.districts.isEmpty ? null : selectedDistrict,
119+
);
120+
}
121+
122+
buildWardPicker(BuildContext context, AddressPickerState state) {
123+
return DropdownButtonFormField<LocationModel>(
124+
decoration: InputDecoration(
125+
labelText: Translate.of(context).translate("ward"),
126+
),
127+
isExpanded: true,
128+
onChanged: (ward) => onWardChanged(ward!),
129+
items: getDropdownItems(state.wards),
130+
value: state.wards.isEmpty ? null : selectedWard,
131+
);
132+
}
133+
134+
135+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:async';
2+
3+
import 'package:bloc/bloc.dart';
4+
import 'package:e_commerce_app/data/models/location_model.dart';
5+
6+
import 'package:e_commerce_app/data/repository/app_repository.dart';
7+
import 'package:e_commerce_app/data/repository/location_repository/location_repo.dart';
8+
9+
import 'address_picker_event.dart';
10+
import 'address_picker_state.dart';
11+
12+
class AddressPickerBloc extends Bloc<AddressPickerEvent, AddressPickerState> {
13+
LocationRepository _locationRepository = AppRepository.locationRepository;
14+
15+
AddressPickerBloc() : super(AddressPickerState());
16+
17+
@override
18+
Stream<AddressPickerState> mapEventToState(AddressPickerEvent event) async* {
19+
if (event is InitialEvent) {
20+
yield* _mapInitialEventToState(event, state);
21+
} else if (event is LoadDistricts) {
22+
yield* _mapLoadDistrictsToState(event, state);
23+
} else if (event is LoadWards) {
24+
yield* _mapLoadWardsToState(event, state);
25+
}
26+
}
27+
28+
Stream<AddressPickerState> _mapInitialEventToState(
29+
InitialEvent event,
30+
AddressPickerState state,
31+
) async* {
32+
try {
33+
List<LocationModel> cities = await _locationRepository.fetchCities();
34+
List<LocationModel> districts = [];
35+
List<LocationModel> wards = [];
36+
if (event.cityId != null) {
37+
districts = await _locationRepository.fetchDistricts(event.cityId!);
38+
}
39+
if (event.districtId != null) {
40+
wards = await _locationRepository.fetchWards(event.districtId!);
41+
}
42+
yield state.cloneWith(
43+
cities: cities,
44+
districts: districts,
45+
wards: wards,
46+
);
47+
} catch (e) {
48+
print(e.toString());
49+
}
50+
}
51+
52+
Stream<AddressPickerState> _mapLoadDistrictsToState(
53+
LoadDistricts event,
54+
AddressPickerState state,
55+
) async* {
56+
try {
57+
var districts = await _locationRepository.fetchDistricts(event.cityId);
58+
yield state.cloneWith(districts: districts, wards: []);
59+
} catch (e) {
60+
print(e.toString());
61+
}
62+
}
63+
64+
Stream<AddressPickerState> _mapLoadWardsToState(
65+
LoadWards event,
66+
AddressPickerState state,
67+
) async* {
68+
try {
69+
var wards = await _locationRepository.fetchWards(event.districtId);
70+
yield state.cloneWith(wards: wards);
71+
} catch (e) {
72+
print(e.toString());
73+
}
74+
}
75+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:equatable/equatable.dart';
2+
3+
abstract class AddressPickerEvent extends Equatable {
4+
const AddressPickerEvent();
5+
6+
@override
7+
List<Object> get props => [];
8+
}
9+
10+
class InitialEvent extends AddressPickerEvent {
11+
final int? cityId;
12+
final int? districtId;
13+
14+
InitialEvent({this.cityId, this.districtId});
15+
}
16+
17+
class LoadDistricts extends AddressPickerEvent {
18+
final int cityId;
19+
20+
LoadDistricts(this.cityId);
21+
22+
List<Object> get props => [cityId];
23+
}
24+
25+
class LoadWards extends AddressPickerEvent {
26+
final int districtId;
27+
28+
LoadWards(this.districtId);
29+
30+
List<Object> get props => [districtId];
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:e_commerce_app/data/models/location_model.dart';
2+
import 'package:equatable/equatable.dart';
3+
4+
class AddressPickerState extends Equatable {
5+
final List<LocationModel> cities;
6+
final List<LocationModel> districts;
7+
final List<LocationModel> wards;
8+
9+
AddressPickerState({
10+
this.cities = const <LocationModel>[],
11+
this.districts = const <LocationModel>[],
12+
this.wards = const <LocationModel>[],
13+
});
14+
15+
AddressPickerState cloneWith({
16+
List<LocationModel>? cities,
17+
List<LocationModel>? districts,
18+
List<LocationModel>? wards,
19+
}) {
20+
return AddressPickerState(
21+
cities: cities ?? this.cities,
22+
districts: districts ?? this.districts,
23+
wards: wards ?? this.wards,
24+
);
25+
}
26+
27+
@override
28+
String toString() {
29+
return "AddressPickerState(cities: ${cities.length}, districts: ${districts.length}, wards: ${wards.length})";
30+
}
31+
32+
@override
33+
List<Object> get props => [cities, districts, wards];
34+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export 'address_picker_bloc.dart';
2+
export 'address_picker_state.dart';
3+
export 'address_picker_event.dart';

0 commit comments

Comments
 (0)