Complete refactoring SFM App Source Code
This commit is contained in:
255
lib/feature/devices/device_update/device_update_bloc.dart
Normal file
255
lib/feature/devices/device_update/device_update_bloc.dart
Normal file
@@ -0,0 +1,255 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
import '../../../product/shared/model/ward_model.dart';
|
||||
import '../../../product/utils/response_status_utils.dart';
|
||||
|
||||
import '../../../product/shared/model/district_model.dart';
|
||||
import '../../../product/shared/model/province_model.dart';
|
||||
import '../device_model.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
|
||||
class DeviceUpdateBloc extends BlocBase {
|
||||
APIServices apiServices = APIServices();
|
||||
|
||||
final deviceInfo = StreamController<Device>.broadcast();
|
||||
StreamSink<Device> get sinkDeviceInfo => deviceInfo.sink;
|
||||
Stream<Device> get streamDeviceInfo => deviceInfo.stream;
|
||||
|
||||
// DeviceUpdateScreen
|
||||
final isChanged = StreamController<bool>.broadcast();
|
||||
StreamSink<bool> get sinkIsChanged => isChanged.sink;
|
||||
Stream<bool> get streamIsChanged => isChanged.stream;
|
||||
|
||||
final provinceData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkProvinceData => provinceData.sink;
|
||||
Stream<Map<String, String>> get streamProvinceData => provinceData.stream;
|
||||
|
||||
final listProvinces =
|
||||
StreamController<List<DropdownMenuItem<Province>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<Province>>> get sinkListProvinces =>
|
||||
listProvinces.sink;
|
||||
Stream<List<DropdownMenuItem<Province>>> get streamListProvinces =>
|
||||
listProvinces.stream;
|
||||
|
||||
final districtData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkDistrictData => districtData.sink;
|
||||
Stream<Map<String, String>> get streamDistrictData => districtData.stream;
|
||||
|
||||
final listDistricts =
|
||||
StreamController<List<DropdownMenuItem<District>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<District>>> get sinkListDistricts =>
|
||||
listDistricts.sink;
|
||||
Stream<List<DropdownMenuItem<District>>> get streamListDistricts =>
|
||||
listDistricts.stream;
|
||||
|
||||
final wardData = StreamController<Map<String, String>>.broadcast();
|
||||
StreamSink<Map<String, String>> get sinkWardData => wardData.sink;
|
||||
Stream<Map<String, String>> get streamWardData => wardData.stream;
|
||||
|
||||
final listWards = StreamController<List<DropdownMenuItem<Ward>>>.broadcast();
|
||||
StreamSink<List<DropdownMenuItem<Ward>>> get sinkListWards => listWards.sink;
|
||||
Stream<List<DropdownMenuItem<Ward>>> get streamListWards => listWards.stream;
|
||||
|
||||
// Show Maps in DeviceUpdateScreen
|
||||
|
||||
final markers = StreamController<Set<Marker>>.broadcast();
|
||||
StreamSink<Set<Marker>> get sinkMarkers => markers.sink;
|
||||
Stream<Set<Marker>> get streamMarkers => markers.stream;
|
||||
|
||||
final searchLocation = StreamController<TextEditingController>.broadcast();
|
||||
StreamSink<TextEditingController> get sinkSearchLocation =>
|
||||
searchLocation.sink;
|
||||
Stream<TextEditingController> get streamSearchLocation =>
|
||||
searchLocation.stream;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// deviceInfo.done;
|
||||
}
|
||||
|
||||
Future<void> getAllProvinces() async {
|
||||
List<DropdownMenuItem<Province>> provincesData = [];
|
||||
provincesData.clear();
|
||||
sinkListProvinces.add(provincesData);
|
||||
final body = await apiServices.getAllProvinces();
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
|
||||
final provinces = Province.fromJsonDynamicList(items);
|
||||
for (var province in provinces) {
|
||||
provincesData.add(
|
||||
DropdownMenuItem(value: province, child: Text(province.fullName!)));
|
||||
}
|
||||
sinkListProvinces.add(provincesData);
|
||||
}
|
||||
|
||||
Future<void> getAllDistricts(String provinceID) async {
|
||||
List<DropdownMenuItem<District>> districtsData = [];
|
||||
districtsData.clear();
|
||||
sinkListDistricts.add(districtsData);
|
||||
final body = await apiServices.getAllDistricts(provinceID);
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
final districts = District.fromJsonDynamicList(items);
|
||||
for (var district in districts) {
|
||||
districtsData.add(
|
||||
DropdownMenuItem(value: district, child: Text(district.fullName!)));
|
||||
}
|
||||
sinkListDistricts.add(districtsData);
|
||||
}
|
||||
|
||||
Future<void> getAllWards(String districtID) async {
|
||||
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||
wardsData.clear();
|
||||
sinkListWards.add(wardsData);
|
||||
final body = await apiServices.getAllWards(districtID);
|
||||
final data = jsonDecode(body);
|
||||
List<dynamic> items = data["items"];
|
||||
final wards = Ward.fromJsonDynamicList(items);
|
||||
for (var ward in wards) {
|
||||
wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!)));
|
||||
}
|
||||
sinkListWards.add(wardsData);
|
||||
}
|
||||
|
||||
Future<void> getDeviceInfomation(
|
||||
String thingID,
|
||||
List<DropdownMenuItem<District>> districtsData,
|
||||
List<DropdownMenuItem<Ward>> wardsData,
|
||||
TextEditingController deviceNameController,
|
||||
TextEditingController latitudeController,
|
||||
TextEditingController longitudeController) async {
|
||||
String body = await apiServices.getDeviceInfomation(thingID);
|
||||
final data = jsonDecode(body);
|
||||
Device device = Device.fromJson(data);
|
||||
sinkDeviceInfo.add(device);
|
||||
deviceNameController.text = device.name ?? "";
|
||||
latitudeController.text = device.settings!.latitude ?? "";
|
||||
longitudeController.text = device.settings!.longitude ?? "";
|
||||
if (device.areaPath != "") {
|
||||
List<String> areaPath = device.areaPath!.split('_');
|
||||
String provinceCode = areaPath[0];
|
||||
String districtCode = areaPath[1];
|
||||
String wardCode = areaPath[2];
|
||||
getAllDistricts(provinceCode);
|
||||
getAllWards(districtCode);
|
||||
final provinceResponse = await apiServices.getProvinceByID(provinceCode);
|
||||
final provincesData = jsonDecode(provinceResponse);
|
||||
Province province = Province.fromJson(provincesData['data']);
|
||||
final districtResponse = await apiServices.getDistrictByID(districtCode);
|
||||
final districtData = jsonDecode(districtResponse);
|
||||
District district = District.fromJson(districtData['data']);
|
||||
final wardResponse = await apiServices.getWardByID(wardCode);
|
||||
final wardData = jsonDecode(wardResponse);
|
||||
Ward ward = Ward.fromJson(wardData['data']);
|
||||
Map<String, String> provinceData = {
|
||||
"name": province.fullName!,
|
||||
"code": province.code!
|
||||
};
|
||||
sinkProvinceData.add(provinceData);
|
||||
Map<String, String> districData = {
|
||||
"name": district.fullName!,
|
||||
"code": district.code!,
|
||||
};
|
||||
sinkDistrictData.add(districData);
|
||||
Map<String, String> wardMap = {
|
||||
"name": ward.fullName!,
|
||||
"code": ward.code!,
|
||||
};
|
||||
sinkWardData.add(wardMap);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Province> getProvinceByName(String name) async {
|
||||
final response = await apiServices.getProvincesByName(name);
|
||||
final data = jsonDecode(response);
|
||||
if (data != null &&
|
||||
data.containsKey('items') &&
|
||||
data['items'] != null &&
|
||||
data['items'].isNotEmpty) {
|
||||
List<dynamic> items = data['items'];
|
||||
List<Province> provinces = Province.fromJsonDynamicList(items);
|
||||
if (provinces.isNotEmpty) {
|
||||
return provinces[0];
|
||||
}
|
||||
}
|
||||
return Province(name: "null");
|
||||
}
|
||||
|
||||
Future<District> getDistrictByName(String name, String provinceCode) async {
|
||||
final response = await apiServices.getDistrictsByName(name);
|
||||
if (response != "") {
|
||||
final data = jsonDecode(response);
|
||||
List<dynamic> items = data['items'];
|
||||
if (items.isNotEmpty) {
|
||||
List<District> districts = District.fromJsonDynamicList(items);
|
||||
if (districts.isNotEmpty) {
|
||||
for (var district in districts) {
|
||||
if (district.provinceCode == provinceCode) {
|
||||
return district;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return District(name: "null");
|
||||
}
|
||||
|
||||
Future<Ward> getWardByName(String name, String districtCode) async {
|
||||
final response = await apiServices.getWarsdByName(name);
|
||||
final data = jsonDecode(response);
|
||||
if (data != null && data['items'] != null) {
|
||||
List<dynamic> items = data['items'];
|
||||
if (items.isNotEmpty) {
|
||||
List<Ward> wards = Ward.fromJsonDynamicList(items);
|
||||
if (wards.isNotEmpty) {
|
||||
for (var ward in wards) {
|
||||
if (ward.districtCode == districtCode) {
|
||||
return ward;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ward(name: "null");
|
||||
}
|
||||
|
||||
Future<void> updateDevice(
|
||||
BuildContext context,
|
||||
String thingID,
|
||||
String name,
|
||||
String latitude,
|
||||
String longitude,
|
||||
String provinceCode,
|
||||
String districtCode,
|
||||
String wardCode,
|
||||
) async {
|
||||
DateTime dateTime = DateTime.now();
|
||||
String formattedDateTime =
|
||||
DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
|
||||
Map<String, dynamic> body = {
|
||||
"name": name,
|
||||
"area_province": provinceCode,
|
||||
"area_district": districtCode,
|
||||
"area_ward": wardCode,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"note": "User updated device infomation at $formattedDateTime",
|
||||
};
|
||||
int statusCode = await apiServices.updateOwnerDevice(thingID, body);
|
||||
showSnackBarResponseByStatusCodeNoIcon(
|
||||
context,
|
||||
statusCode,
|
||||
appLocalization(context).notification_update_device_success,
|
||||
appLocalization(context).notification_update_device_failed,
|
||||
);
|
||||
}
|
||||
}
|
||||
515
lib/feature/devices/device_update/device_update_screen.dart
Normal file
515
lib/feature/devices/device_update/device_update_screen.dart
Normal file
@@ -0,0 +1,515 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:search_choices/search_choices.dart';
|
||||
|
||||
import '../device_model.dart';
|
||||
import 'device_update_bloc.dart';
|
||||
import 'map_dialog.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
|
||||
import '../../../product/shared/model/district_model.dart';
|
||||
import '../../../product/shared/model/province_model.dart';
|
||||
import '../../../product/shared/model/ward_model.dart';
|
||||
|
||||
class DeviceUpdateScreen extends StatefulWidget {
|
||||
const DeviceUpdateScreen({super.key, required this.thingID});
|
||||
final String thingID;
|
||||
@override
|
||||
State<DeviceUpdateScreen> createState() => _DeviceUpdateScreenState();
|
||||
}
|
||||
|
||||
class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
|
||||
late DeviceUpdateBloc deviceUpdateBloc;
|
||||
APIServices apiServices = APIServices();
|
||||
Device device = Device();
|
||||
bool isChanged = false;
|
||||
TextEditingController deviceNameController = TextEditingController();
|
||||
TextEditingController deviceLatitudeController = TextEditingController();
|
||||
TextEditingController deviceLongitudeController = TextEditingController();
|
||||
List<DropdownMenuItem<Province>> provincesData = [];
|
||||
String? selectedProvince = "";
|
||||
List<DropdownMenuItem<District>> districtsData = [];
|
||||
String? selectedDistrict = "";
|
||||
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||
String? selectedWard = "";
|
||||
|
||||
// static String provinceCode = "";
|
||||
// static String districtCode = "";
|
||||
// static String wardCode = "";
|
||||
|
||||
Map<String, String> provinceData = {};
|
||||
Map<String, String> districtData = {};
|
||||
Map<String, String> wardData = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
deviceUpdateBloc = BlocProvider.of(context);
|
||||
deviceUpdateBloc.getAllProvinces();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(appLocalization(context).device_update_title),
|
||||
),
|
||||
body: StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamProvinceData,
|
||||
builder: (context, provinceNameSnapshot) {
|
||||
return StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamDistrictData,
|
||||
builder: (context, districtNameSnapshot) {
|
||||
return StreamBuilder<Map<String, String>>(
|
||||
stream: deviceUpdateBloc.streamWardData,
|
||||
builder: (context, wardNameSnapshot) {
|
||||
return SafeArea(
|
||||
child: StreamBuilder<Device>(
|
||||
stream: deviceUpdateBloc.streamDeviceInfo,
|
||||
initialData: device,
|
||||
builder: (context, deviceInfoSnapshot) {
|
||||
if (deviceInfoSnapshot.data!.thingId == null) {
|
||||
deviceUpdateBloc.getDeviceInfomation(
|
||||
widget.thingID,
|
||||
districtsData,
|
||||
wardsData,
|
||||
deviceNameController,
|
||||
deviceLatitudeController,
|
||||
deviceLongitudeController);
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return StreamBuilder<bool>(
|
||||
stream: deviceUpdateBloc.streamIsChanged,
|
||||
initialData: isChanged,
|
||||
builder: (context, isChangedSnapshot) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${appLocalization(context).input_name_device_device}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
},
|
||||
textInputAction:
|
||||
TextInputAction.next,
|
||||
controller:
|
||||
deviceNameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.input_name_device_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_location}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context
|
||||
.dynamicWidth(0.6),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
},
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.next,
|
||||
controller:
|
||||
deviceLatitudeController,
|
||||
decoration:
|
||||
InputDecoration(
|
||||
label: Text(
|
||||
"${appLocalization(context).update_device_dialog_location_longitude}:"),
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_longitude_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: context
|
||||
.paddingLowVertical,
|
||||
child: SizedBox(
|
||||
child: TextField(
|
||||
onChanged:
|
||||
(value) {
|
||||
isChangedListener();
|
||||
},
|
||||
controller:
|
||||
deviceLongitudeController,
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.next,
|
||||
decoration:
|
||||
InputDecoration(
|
||||
label: Text(
|
||||
"${appLocalization(context).update_device_dialog_location_latitude}:"),
|
||||
hintText: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_latitude_hintText,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: IconButton.filled(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(
|
||||
Colors
|
||||
.lightGreen)),
|
||||
// iconSize: 24,
|
||||
onPressed: () async {
|
||||
showMapDialog(
|
||||
context,
|
||||
deviceUpdateBloc,
|
||||
deviceLatitudeController,
|
||||
deviceLongitudeController);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.map_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.lowValue),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_province}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<
|
||||
DropdownMenuItem<
|
||||
Province>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListProvinces,
|
||||
builder: (dialogContext,
|
||||
listProvinces) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(),
|
||||
),
|
||||
child: SearchChoices.single(
|
||||
items:
|
||||
listProvinces.data ??
|
||||
provincesData,
|
||||
hint: provinceNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
provinceNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
"",
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_province_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_province_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// provinceCode =
|
||||
// value.code;
|
||||
selectedProvince =
|
||||
value.fullName;
|
||||
provinceData['name'] =
|
||||
value.fullName;
|
||||
provinceData['code'] =
|
||||
value.code;
|
||||
deviceUpdateBloc
|
||||
.sinkProvinceData
|
||||
.add(provinceData);
|
||||
deviceUpdateBloc
|
||||
.getAllDistricts(
|
||||
value.code);
|
||||
selectedDistrict = "";
|
||||
districtData['name'] =
|
||||
selectedDistrict!;
|
||||
// deviceUpdateBloc.sinkDistrictName
|
||||
// .add(selectedDistrict);
|
||||
deviceUpdateBloc
|
||||
.sinkDistrictData
|
||||
.add(districtData);
|
||||
selectedWard = "";
|
||||
wardData['name'] =
|
||||
selectedWard!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_district}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<
|
||||
DropdownMenuItem<
|
||||
District>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListDistricts,
|
||||
builder: (dialogContext,
|
||||
listDistricts) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(),
|
||||
),
|
||||
child: SearchChoices.single(
|
||||
items:
|
||||
listDistricts.data ??
|
||||
districtsData,
|
||||
hint: districtNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
districtNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
selectedDistrict!,
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_district_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_district_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// districtCode =
|
||||
// value.code;
|
||||
selectedDistrict =
|
||||
value.fullName;
|
||||
districtData['name'] =
|
||||
value.fullName!;
|
||||
districtData['code'] =
|
||||
value.code;
|
||||
deviceUpdateBloc
|
||||
.sinkDistrictData
|
||||
.add(districtData);
|
||||
deviceUpdateBloc
|
||||
.getAllWards(
|
||||
value.code);
|
||||
selectedWard = "";
|
||||
wardData['name'] =
|
||||
selectedWard!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${appLocalization(context).device_update_ward}:",
|
||||
style: context
|
||||
.titleMediumTextStyle),
|
||||
Padding(
|
||||
padding:
|
||||
context.paddingLowVertical,
|
||||
child: StreamBuilder<
|
||||
List<DropdownMenuItem<Ward>>>(
|
||||
stream: deviceUpdateBloc
|
||||
.streamListWards,
|
||||
builder:
|
||||
(dialogContext, listWards) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius
|
||||
.all(
|
||||
Radius.circular(
|
||||
20)),
|
||||
border: Border.all()),
|
||||
child: SearchChoices.single(
|
||||
items: listWards.data ??
|
||||
wardsData,
|
||||
hint: wardNameSnapshot
|
||||
.data !=
|
||||
null
|
||||
? Text(
|
||||
wardNameSnapshot
|
||||
.data![
|
||||
'name'] ??
|
||||
selectedWard!,
|
||||
)
|
||||
: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_ward_hintText,
|
||||
searchHint: appLocalization(
|
||||
context)
|
||||
.update_device_dialog_location_ward_searchHint,
|
||||
displayClearIcon: false,
|
||||
onChanged: (value) {
|
||||
isChangedListener();
|
||||
// wardCode = value.code;
|
||||
selectedWard =
|
||||
value.fullName;
|
||||
wardData['name'] =
|
||||
value.fullName!;
|
||||
wardData['code'] =
|
||||
value.code!;
|
||||
deviceUpdateBloc
|
||||
.sinkWardData
|
||||
.add(wardData);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isChangedSnapshot.data == true)
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width:
|
||||
context.dynamicWidth(0.6),
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty
|
||||
.all(Colors
|
||||
.white),
|
||||
backgroundColor:
|
||||
MaterialStateProperty
|
||||
.all(Colors
|
||||
.blue)),
|
||||
onPressed: () async {
|
||||
String provinceCode =
|
||||
provinceNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String districtCode =
|
||||
districtNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String wardCode =
|
||||
wardNameSnapshot
|
||||
.data![
|
||||
"code"] ??
|
||||
"";
|
||||
String latitude =
|
||||
deviceLatitudeController
|
||||
.value.text;
|
||||
String longitude =
|
||||
deviceLongitudeController
|
||||
.value.text;
|
||||
String deviceName =
|
||||
deviceNameController
|
||||
.value.text;
|
||||
// log("ProvinceCode: $provinceCode");
|
||||
// log("DistrictCode: $districtCode");
|
||||
// log("WardCode: $wardCode");
|
||||
// log("Latitude: $latitude");
|
||||
// log("Longitude: $longitude");
|
||||
// log("Device Name: $deviceName");
|
||||
await deviceUpdateBloc
|
||||
.updateDevice(
|
||||
context,
|
||||
deviceInfoSnapshot
|
||||
.data!.thingId!,
|
||||
deviceName,
|
||||
latitude,
|
||||
longitude,
|
||||
provinceCode,
|
||||
districtCode,
|
||||
wardCode,
|
||||
);
|
||||
Future.delayed(
|
||||
// ignore: use_build_context_synchronously
|
||||
context.lowDuration,
|
||||
() {
|
||||
Navigator.pop(
|
||||
context);
|
||||
});
|
||||
},
|
||||
child: Text(appLocalization(
|
||||
context)
|
||||
.update_button_content)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void isChangedListener() {
|
||||
isChanged = true;
|
||||
deviceUpdateBloc.sinkIsChanged.add(isChanged);
|
||||
}
|
||||
}
|
||||
47
lib/feature/devices/device_update/geocode_model.dart
Normal file
47
lib/feature/devices/device_update/geocode_model.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class Geocode {
|
||||
List<AddressComponent>? addressComponents;
|
||||
String? formattedAddress;
|
||||
Geocode({
|
||||
this.addressComponents,
|
||||
this.formattedAddress,
|
||||
});
|
||||
|
||||
Geocode.fromJson(Map<String, dynamic> json) {
|
||||
formattedAddress = json['formatted_address'];
|
||||
if (json['address_components'] != null) {
|
||||
addressComponents = [];
|
||||
json['address_components'].forEach((v) {
|
||||
addressComponents!.add(AddressComponent.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['formatted_address'] = formattedAddress;
|
||||
if (addressComponents != null) {
|
||||
data['address_components'] =
|
||||
addressComponents!.map((e) => e.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class AddressComponent {
|
||||
String? longName;
|
||||
String? shortName;
|
||||
List<String>? types;
|
||||
AddressComponent({this.longName, this.shortName, this.types});
|
||||
|
||||
AddressComponent.fromJson(Map<String, dynamic> json) {
|
||||
longName = json['long_name'];
|
||||
shortName = json['short_name'];
|
||||
types = json['types'].cast<String>();
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['long_name'] = longName;
|
||||
data['short_name'] = shortName;
|
||||
data['type'] = types;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
298
lib/feature/devices/device_update/map_dialog.dart
Normal file
298
lib/feature/devices/device_update/map_dialog.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'device_update_bloc.dart';
|
||||
import '../../../product/constant/app/app_constants.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
import '../../../product/shared/find_location_maps/shared_map_search_location.dart';
|
||||
|
||||
import '../../../product/shared/find_location_maps/model/prediction_model.dart';
|
||||
import '../../../product/shared/shared_transition.dart';
|
||||
import 'geocode_model.dart';
|
||||
|
||||
showMapDialog(
|
||||
BuildContext context,
|
||||
DeviceUpdateBloc deviceUpdateBloc,
|
||||
TextEditingController latitudeController,
|
||||
TextEditingController longitudeController) async {
|
||||
const CameraPosition defaultPosition = CameraPosition(
|
||||
target: LatLng(20.985424, 105.738354),
|
||||
zoom: 12,
|
||||
);
|
||||
TextEditingController searchLocationController = TextEditingController();
|
||||
TextEditingController mapDialogLatitudeController = TextEditingController();
|
||||
TextEditingController mapDialogLongitudeController = TextEditingController();
|
||||
Completer<GoogleMapController> ggmapController = Completer();
|
||||
final streamController = StreamController<GoogleMapController>.broadcast();
|
||||
showGeneralDialog(
|
||||
barrierDismissible: false,
|
||||
transitionDuration: context.normalDuration,
|
||||
transitionBuilder: transitionsLeftToRight,
|
||||
context: context,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return StreamBuilder<Set<Marker>>(
|
||||
stream: deviceUpdateBloc.streamMarkers,
|
||||
builder: (context, markerSnapshot) {
|
||||
if (!markerSnapshot.hasData) {
|
||||
if (latitudeController.value.text != "" &&
|
||||
longitudeController.value.text != "") {
|
||||
double latitude = double.parse(latitudeController.text);
|
||||
double longitude = double.parse(longitudeController.text);
|
||||
addMarker(
|
||||
LatLng(latitude, longitude),
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context)
|
||||
.update_device_dialog_maps_dialog_title),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
String latitude = mapDialogLatitudeController.text;
|
||||
String longitude = mapDialogLongitudeController.text;
|
||||
log("Finish -- Latitude: $latitude, longitude: $longitude --");
|
||||
getDataFromApi(latitude, longitude, deviceUpdateBloc);
|
||||
latitudeController.text =
|
||||
mapDialogLatitudeController.text;
|
||||
longitudeController.text =
|
||||
mapDialogLongitudeController.text;
|
||||
bool isChange = true;
|
||||
deviceUpdateBloc.sinkIsChanged.add(isChange);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.check))
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
onTap: (location) async {
|
||||
addMarker(
|
||||
location,
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController,
|
||||
);
|
||||
},
|
||||
markers: markerSnapshot.data ?? {},
|
||||
onMapCreated: (GoogleMapController mapController) {
|
||||
ggmapController.complete(mapController);
|
||||
streamController.add(mapController);
|
||||
},
|
||||
initialCameraPosition: defaultPosition,
|
||||
),
|
||||
Container(
|
||||
// color: Colors.white,
|
||||
height: 80,
|
||||
alignment: Alignment.topCenter,
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: StreamBuilder<TextEditingController>(
|
||||
stream: deviceUpdateBloc.streamSearchLocation,
|
||||
builder: (context, searchLocation) {
|
||||
return NearBySearchSFM(
|
||||
textInputAction: TextInputAction.done,
|
||||
textEditingController:
|
||||
searchLocation.data ?? searchLocationController,
|
||||
googleAPIKey: ApplicationConstants.MAP_KEY,
|
||||
locationLatitude: 20.985424,
|
||||
locationLongitude: 105.738354,
|
||||
radius: 50000,
|
||||
inputDecoration: InputDecoration(
|
||||
hintText: appLocalization(context)
|
||||
.update_device_dialog_search_location_hint,
|
||||
border: InputBorder.none),
|
||||
debounceTime: 600,
|
||||
isLatLngRequired: true,
|
||||
getPlaceDetailWithLatLng: (Prediction prediction) {
|
||||
FocusScope.of(context).unfocus();
|
||||
addMarker(
|
||||
LatLng(double.parse(prediction.lat!),
|
||||
double.parse(prediction.lng!)),
|
||||
ggmapController,
|
||||
deviceUpdateBloc,
|
||||
mapDialogLatitudeController,
|
||||
mapDialogLongitudeController);
|
||||
},
|
||||
itemClick: (Prediction prediction) {
|
||||
searchLocationController.text =
|
||||
prediction.structuredFormatting!.mainText!;
|
||||
deviceUpdateBloc.sinkSearchLocation
|
||||
.add(searchLocationController);
|
||||
searchLocationController.selection =
|
||||
TextSelection.fromPosition(TextPosition(
|
||||
offset: prediction.structuredFormatting!
|
||||
.mainText!.length));
|
||||
},
|
||||
boxDecoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20))),
|
||||
);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
addMarker(
|
||||
LatLng position,
|
||||
Completer<GoogleMapController> mapController,
|
||||
DeviceUpdateBloc deviceUpdateBloc,
|
||||
TextEditingController mapDialogLatitudeController,
|
||||
TextEditingController mapDialogLongitudeController,
|
||||
) async {
|
||||
log("AddMarker -- Latitude: ${position.latitude}, longitude: ${position.longitude} --");
|
||||
|
||||
Set<Marker> marker = {};
|
||||
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||
Marker newMarker = Marker(
|
||||
markerId: const MarkerId('value'),
|
||||
position: LatLng(position.latitude, position.longitude),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||
draggable: true,
|
||||
onDragEnd: (position) {
|
||||
mapDialogLatitudeController.text = position.latitude.toString();
|
||||
mapDialogLongitudeController.text = position.longitude.toString();
|
||||
},
|
||||
);
|
||||
marker.add(newMarker);
|
||||
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||
mapDialogLatitudeController.text = position.latitude.toString();
|
||||
mapDialogLongitudeController.text = position.longitude.toString();
|
||||
updateCameraPosition(position, 14, mapController);
|
||||
}
|
||||
|
||||
void getDataFromApi(String latitude, String longitude,
|
||||
DeviceUpdateBloc deviceUpdateBloc) async {
|
||||
String path =
|
||||
"maps/api/geocode/json?latlng=$latitude,$longitude&language=vi&result_type=political&key=${ApplicationConstants.MAP_KEY}";
|
||||
var url = Uri.parse('https://maps.googleapis.com/$path');
|
||||
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
log("Loi: ${response.statusCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, dynamic> data = jsonDecode(response.body);
|
||||
if (!data.containsKey('results') || data['results'].isEmpty) {
|
||||
log("Khong co result");
|
||||
return;
|
||||
}
|
||||
|
||||
List<dynamic> results = data['results'];
|
||||
List<Geocode> geocodes =
|
||||
results.map((result) => Geocode.fromJson(result)).toList();
|
||||
|
||||
Map<String, String> locations =
|
||||
_extractLocationComponents(geocodes[0].addressComponents!);
|
||||
|
||||
// In ra thông tin của các location
|
||||
locations.forEach((key, value) {
|
||||
log("$key: $value");
|
||||
});
|
||||
|
||||
await _processLocations(locations, deviceUpdateBloc);
|
||||
}
|
||||
|
||||
Map<String, String> _extractLocationComponents(
|
||||
List<AddressComponent> addressComponents) {
|
||||
Map<String, String> locations = {};
|
||||
|
||||
for (var addressComponent in addressComponents) {
|
||||
String longName = addressComponent.longName ?? "";
|
||||
if (addressComponent.types!.contains('administrative_area_level_3') ||
|
||||
addressComponent.types!.contains('sublocality_level_1')) {
|
||||
locations['wardkey'] = longName;
|
||||
} else if (addressComponent.types!
|
||||
.contains('administrative_area_level_2') ||
|
||||
addressComponent.types!.contains('sublocality_level_2') ||
|
||||
addressComponent.types!.contains('locality')) {
|
||||
locations['districtkey'] = longName;
|
||||
} else if (addressComponent.types!
|
||||
.contains('administrative_area_level_1')) {
|
||||
locations['provincekey'] = longName;
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
Future<void> _processLocations(
|
||||
Map<String, String> locations, DeviceUpdateBloc deviceUpdateBloc) async {
|
||||
String provinceNameFromAPI = locations['provincekey'] ?? "";
|
||||
String districtNameFromAPI = locations['districtkey'] ?? "";
|
||||
String wardNameFromAPI = locations['wardkey'] ?? "";
|
||||
|
||||
final province =
|
||||
await deviceUpdateBloc.getProvinceByName(provinceNameFromAPI);
|
||||
if (province.name != "null") {
|
||||
log("Province: ${province.fullName}, ProvinceCode: ${province.code}");
|
||||
deviceUpdateBloc.sinkProvinceData
|
||||
.add({"code": province.code!, "name": province.fullName!});
|
||||
deviceUpdateBloc.getAllProvinces();
|
||||
|
||||
final district = await deviceUpdateBloc.getDistrictByName(
|
||||
districtNameFromAPI, province.code!);
|
||||
log("Districtname: ${district.fullName}, districtCode: ${district.code}");
|
||||
deviceUpdateBloc.getAllDistricts(province.code!);
|
||||
if (district.name != "null") {
|
||||
deviceUpdateBloc.sinkDistrictData
|
||||
.add({"code": district.code!, "name": district.fullName!});
|
||||
final ward =
|
||||
await deviceUpdateBloc.getWardByName(wardNameFromAPI, district.code!);
|
||||
log("Wardname: ${ward.fullName}, WardCode: ${ward.code}");
|
||||
deviceUpdateBloc.getAllWards(district.code!);
|
||||
if (ward.name != "null") {
|
||||
log("Xac dinh duoc het thong tin tu toa do");
|
||||
deviceUpdateBloc.sinkWardData
|
||||
.add({"code": ward.code!, "name": ward.fullName!});
|
||||
} else {
|
||||
deviceUpdateBloc.sinkWardData.add({});
|
||||
}
|
||||
} else {
|
||||
deviceUpdateBloc.sinkDistrictData.add({});
|
||||
}
|
||||
} else {
|
||||
deviceUpdateBloc.sinkProvinceData.add({});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateCameraPosition(LatLng location, double zoom,
|
||||
Completer<GoogleMapController> mapController) async {
|
||||
final CameraPosition cameraPosition = CameraPosition(
|
||||
target: LatLng(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
),
|
||||
zoom: zoom,
|
||||
);
|
||||
final GoogleMapController mapControllerNew = await mapController.future;
|
||||
mapControllerNew.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
cameraPosition,
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user