Complete refactoring SFM App Source Code

This commit is contained in:
anhtunz
2024-12-15 00:59:02 +07:00
parent caa73ca43c
commit 2e27d59278
247 changed files with 18390 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
import 'package:dio/dio.dart';
import 'error_response_model.dart';
class DioErrorHandler {
ErrorResponse errorResponse = ErrorResponse();
String errorDescription = "";
ErrorResponse handleDioError(DioException dioError) {
switch (dioError.type) {
case DioExceptionType.cancel:
errorResponse.message = "Request to API server was cancelled";
break;
case DioExceptionType.connectionTimeout:
errorResponse.message = "Connection timeout with API server";
break;
case DioExceptionType.unknown:
if ((dioError.message!.contains("RedirectException"))) {
errorResponse.message = dioError.message;
} else {
errorResponse.message = "Please check the internet connection";
}
break;
case DioExceptionType.receiveTimeout:
errorResponse.message = "Receive timeout in connection with API server";
break;
case DioExceptionType.badResponse:
try {
if (dioError.response?.data['message'] != null) {
errorResponse.message = dioError.response?.data['message'];
} else {
if ((dioError.response?.statusMessage ?? "").isNotEmpty) {
errorResponse.message = dioError.response?.statusMessage;
} else {
return _handleError(
dioError.response!.statusCode, dioError.response!.data);
}
}
} catch (e) {
if ((dioError.response?.statusMessage ?? "").isNotEmpty) {
errorResponse.message = dioError.response?.statusMessage;
} else {
return _handleError(
dioError.response!.statusCode, dioError.response!.data);
}
}
break;
case DioExceptionType.sendTimeout:
errorResponse.message = "Send timeout in connection with API server";
break;
default:
errorResponse.message = "Something went wrong";
break;
}
return errorResponse;
}
ErrorResponse _handleError(int? statusCode, dynamic error) {
switch (statusCode) {
case 400:
return getMas(error);
// case 401:
// return checkTokenExpire(error);
case 404:
return getMas(error);
case 403:
return getMas(error);
case 500:
errorResponse.message = 'Internal server error';
return errorResponse;
default:
return getUnKnownMes(error);
}
}
// checkTokenExpire(error) {
// // print("my error ${error}");
// if (error['msg'].toString().toLowerCase() ==
// "Token has expired".toLowerCase()) {
// UIData.tokenExpire(error['msg']);
// return;
// }
// errorResponse.message = error['msg'].toString();
// return errorResponse;
// }
getMas(dynamic error) {
print("myError ${error.runtimeType}");
if (error.runtimeType != String) {
errorResponse.message =
error['message'].toString(); //?? S.of(Get.context).something_wrong;
} else {
if (error['msg'] != null) {
errorResponse.message = error['msg'].toString();
} else {
errorResponse.message = "Something Wrong";
} //S.of(Get.context).something_wrong;
}
return errorResponse;
}
getUnKnownMes(dynamic error) {
if (error['msg'] != null) {
errorResponse.message = error['msg'].toString();
} else if (error['message'] != null) {
errorResponse.message = error['message'].toString();
} else {
errorResponse.message = "Something went wrong";
}
return errorResponse;
}
}
class ErrorHandler {
static final ErrorHandler _inst = ErrorHandler.internal();
ErrorHandler.internal();
factory ErrorHandler() {
return _inst;
}
ErrorResponse errorResponse = ErrorResponse();
ErrorResponse handleError(var error) {
if (error.runtimeType.toString().toLowerCase() ==
"_TypeError".toLowerCase()) {
// return error.toString();
errorResponse.message = "The Provided API key is invalid";
return errorResponse;
} else if (error is DioError) {
return DioErrorHandler().handleDioError(error);
}
errorResponse.message = "The Provided API key is invalid";
return errorResponse;
}
}

View File

@@ -0,0 +1,19 @@
class ErrorResponse {
String? message;
int? status;
ErrorResponse({this.message, this.status});
ErrorResponse.fromJson(Map<String, dynamic> json) {
message = json['message'];
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['message'] = message;
data['status'] = status;
return data;
}
}

View File

@@ -0,0 +1,234 @@
class PlaceDetails {
Result? result;
String? status;
PlaceDetails({this.result, this.status});
PlaceDetails.fromJson(Map<String, dynamic> json) {
result =
json['result'] != null ? Result.fromJson(json['result']) : null;
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (result != null) {
data['result'] = result!.toJson();
}
data['status'] = status;
return data;
}
}
class Result {
List<AddressComponents>? addressComponents;
String? adrAddress;
String? formattedAddress;
Geometry? geometry;
String? icon;
String? name;
List<Photos>? photos;
String? placeId;
String? reference;
String? scope;
List<String>? types;
String? url;
int? utcOffset;
String? vicinity;
String? website;
Result(
{this.addressComponents,
this.adrAddress,
this.formattedAddress,
this.geometry,
this.icon,
this.name,
this.photos,
this.placeId,
this.reference,
this.scope,
this.types,
this.url,
this.utcOffset,
this.vicinity,
this.website});
Result.fromJson(Map<String, dynamic> json) {
if (json['address_components'] != null) {
addressComponents = [];
json['address_components'].forEach((v) {
addressComponents!.add(AddressComponents.fromJson(v));
});
}
adrAddress = json['adr_address'];
formattedAddress = json['formatted_address'];
geometry = json['geometry'] != null
? Geometry.fromJson(json['geometry'])
: null;
icon = json['icon'];
name = json['name'];
if (json['photos'] != null) {
photos = [];
json['photos'].forEach((v) {
photos!.add(Photos.fromJson(v));
});
}
placeId = json['place_id'];
reference = json['reference'];
scope = json['scope'];
types = json['types'].cast<String>();
url = json['url'];
utcOffset = json['utc_offset'];
vicinity = json['vicinity'];
website = json['website'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (addressComponents != null) {
data['address_components'] =
addressComponents!.map((v) => v.toJson()).toList();
}
data['adr_address'] = adrAddress;
data['formatted_address'] = formattedAddress;
if (geometry != null) {
data['geometry'] = geometry!.toJson();
}
data['icon'] = icon;
data['name'] = name;
if (photos != null) {
data['photos'] = photos!.map((v) => v.toJson()).toList();
}
data['place_id'] = placeId;
data['reference'] = reference;
data['scope'] = scope;
data['types'] = types;
data['url'] = url;
data['utc_offset'] = utcOffset;
data['vicinity'] = vicinity;
data['website'] = website;
return data;
}
}
class AddressComponents {
String? longName;
String? shortName;
List<String>? types;
AddressComponents({this.longName, this.shortName, this.types});
AddressComponents.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['types'] = types;
return data;
}
}
class Geometry {
Location? location;
Viewport? viewport;
Geometry({this.location, this.viewport});
Geometry.fromJson(Map<String, dynamic> json) {
location = json['location'] != null
? Location.fromJson(json['location'])
: null;
viewport = json['viewport'] != null
? Viewport.fromJson(json['viewport'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (location != null) {
data['location'] = location!.toJson();
}
if (viewport != null) {
data['viewport'] = viewport!.toJson();
}
return data;
}
}
class Location {
double? lat;
double? lng;
Location({this.lat, this.lng});
Location.fromJson(Map<String, dynamic> json) {
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class Viewport {
Location? northeast;
Location? southwest;
Viewport({this.northeast, this.southwest});
Viewport.fromJson(Map<String, dynamic> json) {
northeast = json['northeast'] != null
? Location.fromJson(json['northeast'])
: null;
southwest = json['southwest'] != null
? Location.fromJson(json['southwest'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (northeast != null) {
data['northeast'] = northeast!.toJson();
}
if (southwest != null) {
data['southwest'] = southwest!.toJson();
}
return data;
}
}
class Photos {
int? height;
List<String>? htmlAttributions;
String? photoReference;
int? width;
Photos({this.height, this.htmlAttributions, this.photoReference, this.width});
Photos.fromJson(Map<String, dynamic> json) {
height = json['height'];
htmlAttributions = json['html_attributions'].cast<String>();
photoReference = json['photo_reference'];
width = json['width'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['height'] = height;
data['html_attributions'] = htmlAttributions;
data['photo_reference'] = photoReference;
data['width'] = width;
return data;
}
}

View File

@@ -0,0 +1,157 @@
class PlacesAutocompleteResponse {
List<Prediction>? predictions;
String? status;
PlacesAutocompleteResponse({this.predictions, this.status});
PlacesAutocompleteResponse.fromJson(Map<String, dynamic> json) {
if (json['predictions'] != null) {
predictions = [];
json['predictions'].forEach((v) {
predictions!.add(Prediction.fromJson(v));
});
}
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (predictions != null) {
data['predictions'] = predictions!.map((v) => v.toJson()).toList();
}
data['status'] = status;
return data;
}
}
class Prediction {
String? description;
String? id;
List<MatchedSubstrings>? matchedSubstrings;
String? placeId;
String? reference;
StructuredFormatting? structuredFormatting;
List<Terms>? terms;
List<String>? types;
String? lat;
String? lng;
Prediction(
{this.description,
this.id,
this.matchedSubstrings,
this.placeId,
this.reference,
this.structuredFormatting,
this.terms,
this.types,
this.lat,
this.lng});
Prediction.fromJson(Map<String, dynamic> json) {
description = json['description'];
id = json['id'];
if (json['matched_substrings'] != null) {
matchedSubstrings = [];
json['matched_substrings'].forEach((v) {
matchedSubstrings!.add(MatchedSubstrings.fromJson(v));
});
}
placeId = json['place_id'];
reference = json['reference'];
structuredFormatting = json['structured_formatting'] != null
? StructuredFormatting.fromJson(json['structured_formatting'])
: null;
if (json['terms'] != null) {
terms = [];
json['terms'].forEach((v) {
terms!.add(Terms.fromJson(v));
});
}
types = json['types'].cast<String>();
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['description'] = description;
data['id'] = id;
if (matchedSubstrings != null) {
data['matched_substrings'] =
matchedSubstrings!.map((v) => v.toJson()).toList();
}
data['place_id'] = placeId;
data['reference'] = reference;
if (structuredFormatting != null) {
data['structured_formatting'] = structuredFormatting!.toJson();
}
if (terms != null) {
data['terms'] = terms!.map((v) => v.toJson()).toList();
}
data['types'] = types;
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class MatchedSubstrings {
int? length;
int? offset;
MatchedSubstrings({this.length, this.offset});
MatchedSubstrings.fromJson(Map<String, dynamic> json) {
length = json['length'];
offset = json['offset'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['length'] = length;
data['offset'] = offset;
return data;
}
}
class StructuredFormatting {
String? mainText;
String? secondaryText;
StructuredFormatting({this.mainText, this.secondaryText});
StructuredFormatting.fromJson(Map<String, dynamic> json) {
mainText = json['main_text'];
secondaryText = json['secondary_text'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['main_text'] = mainText;
data['secondary_text'] = secondaryText;
return data;
}
}
class Terms {
int? offset;
String? value;
Terms({this.offset, this.value});
Terms.fromJson(Map<String, dynamic> json) {
offset = json['offset'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['offset'] = offset;
data['value'] = value;
return data;
}
}

View File

@@ -0,0 +1,313 @@
// ignore_for_file: unnecessary_this, use_build_context_synchronously, prefer_typing_uninitialized_variables, library_private_types_in_public_api
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:rxdart/rxdart.dart';
import 'error/dio_handle_error.dart';
import 'model/place_detail_model.dart';
import 'model/prediction_model.dart';
// ignore: must_be_immutable
class NearBySearchSFM extends StatefulWidget {
InputDecoration inputDecoration;
ItemClick? itemClick;
GetPlaceDetailswWithLatLng? getPlaceDetailWithLatLng;
bool isLatLngRequired = true;
double locationLatitude;
double locationLongitude;
int radius;
TextStyle textStyle;
String googleAPIKey;
int debounceTime = 600;
List<String>? countries = [];
TextEditingController textEditingController = TextEditingController();
ListItemBuilder? itemBuilder;
TextInputAction? textInputAction;
Widget? seperatedBuilder;
void clearData;
BoxDecoration? boxDecoration;
bool isCrossBtnShown;
bool showError;
NearBySearchSFM(
{super.key,
required this.textEditingController,
required this.googleAPIKey,
required this.locationLatitude,
required this.locationLongitude,
required this.radius,
this.debounceTime = 600,
this.inputDecoration = const InputDecoration(),
this.itemClick,
this.isLatLngRequired = true,
this.textStyle = const TextStyle(),
this.countries,
this.getPlaceDetailWithLatLng,
this.itemBuilder,
this.boxDecoration,
this.isCrossBtnShown = true,
this.seperatedBuilder,
this.showError = true,
this.textInputAction});
@override
_NearBySearchSFMState createState() =>
_NearBySearchSFMState();
}
class _NearBySearchSFMState
extends State<NearBySearchSFM> {
final subject = PublishSubject<String>();
OverlayEntry? _overlayEntry;
List<Prediction> alPredictions = [];
TextEditingController controller = TextEditingController();
final LayerLink _layerLink = LayerLink();
bool isSearched = false;
bool isCrossBtn = true;
late var _dio;
CancelToken? _cancelToken = CancelToken();
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
alignment: Alignment.centerLeft,
decoration: widget.boxDecoration ??
BoxDecoration(
shape: BoxShape.rectangle,
border: Border.all(color: Colors.grey, width: 0.6),
borderRadius: const BorderRadius.all(Radius.circular(10))),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextFormField(
decoration: widget.inputDecoration,
style: widget.textStyle,
controller: widget.textEditingController,
onChanged: (string) {
subject.add(string);
if (widget.isCrossBtnShown) {
isCrossBtn = string.isNotEmpty ? true : false;
setState(() {});
}
},
),
),
(!widget.isCrossBtnShown)
? const SizedBox()
: isCrossBtn && _showCrossIconWidget()
? IconButton(onPressed: clearData, icon: Icon(Icons.close))
: const SizedBox()
],
),
),
);
}
getLocation(
String text, double latitude, double longitude, int radius) async {
// String url =
// "https://maps.googleapis.com/maps/api/place/nearbysearch/json?keyword=$text&location=$latitude%2C$longitude&radius=$radius&type=hospital|pharmacy|doctor&key=${widget.googleAPIKey}";
String url =
"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$text&location=$latitude%2C$longitude&radius=$radius&strictbounds=true&key=${widget.googleAPIKey}";
log(url);
if (widget.countries != null) {
for (int i = 0; i < widget.countries!.length; i++) {
String country = widget.countries![i];
if (i == 0) {
url = url + "&components=country:$country";
} else {
url = url + "|" + "country:" + country;
}
}
}
if (_cancelToken?.isCancelled == false) {
_cancelToken?.cancel();
_cancelToken = CancelToken();
}
try {
Response response = await _dio.get(url);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
Map map = response.data;
if (map.containsKey("error_message")) {
throw response.data;
}
PlacesAutocompleteResponse subscriptionResponse =
PlacesAutocompleteResponse.fromJson(response.data);
if (text.length == 0) {
alPredictions.clear();
this._overlayEntry!.remove();
return;
}
isSearched = false;
alPredictions.clear();
if (subscriptionResponse.predictions!.isNotEmpty &&
(widget.textEditingController.text.toString().trim()).isNotEmpty) {
alPredictions.addAll(subscriptionResponse.predictions!);
}
this._overlayEntry = null;
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry!);
} catch (e) {
var errorHandler = ErrorHandler.internal().handleError(e);
_showSnackBar("${errorHandler.message}");
}
}
@override
void initState() {
super.initState();
_dio = Dio();
subject.stream
.distinct()
.debounceTime(Duration(milliseconds: widget.debounceTime))
.listen(textChanged);
}
textChanged(String text) async {
getLocation(text, widget.locationLatitude, widget.locationLongitude,
widget.radius);
}
OverlayEntry? _createOverlayEntry() {
if (context.findRenderObject() != null) {
RenderBox renderBox = context.findRenderObject() as RenderBox;
var size = renderBox.size;
var offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) => Positioned(
left: offset.dx,
top: size.height + offset.dy,
width: size.width,
child: CompositedTransformFollower(
showWhenUnlinked: false,
link: this._layerLink,
offset: Offset(0.0, size.height + 5.0),
child: Material(
child: ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: alPredictions.length,
separatorBuilder: (context, pos) =>
widget.seperatedBuilder ?? SizedBox(),
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
var selectedData = alPredictions[index];
if (index < alPredictions.length) {
widget.itemClick!(selectedData);
if (widget.isLatLngRequired) {
getPlaceDetailsFromPlaceId(selectedData);
}
removeOverlay();
}
},
child: widget.itemBuilder != null
? widget.itemBuilder!(
context, index, alPredictions[index])
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10)),
padding: const EdgeInsets.all(5),
child: Text(alPredictions[index].description!)),
);
},
)),
),
));
}
return null;
}
removeOverlay() {
alPredictions.clear();
this._overlayEntry = this._createOverlayEntry();
Overlay.of(context).insert(this._overlayEntry!);
this._overlayEntry!.markNeedsBuild();
}
Future<Response?> getPlaceDetailsFromPlaceId(Prediction prediction) async {
//String key = GlobalConfiguration().getString('google_maps_key');
var url =
"https://maps.googleapis.com/maps/api/place/details/json?placeid=${prediction.placeId}&key=${widget.googleAPIKey}";
Response response = await Dio().get(
url,
);
PlaceDetails placeDetails = PlaceDetails.fromJson(response.data);
prediction.lat = placeDetails.result!.geometry!.location!.lat.toString();
prediction.lng = placeDetails.result!.geometry!.location!.lng.toString();
widget.getPlaceDetailWithLatLng!(prediction);
return null;
}
void clearData() {
widget.textEditingController.clear();
if (_cancelToken?.isCancelled == false) {
_cancelToken?.cancel();
}
setState(() {
alPredictions.clear();
isCrossBtn = false;
});
if (this._overlayEntry != null) {
try {
this._overlayEntry?.remove();
} catch (e) {}
}
}
_showCrossIconWidget() {
return (widget.textEditingController.text.isNotEmpty);
}
_showSnackBar(String errorData) {
if (widget.showError) {
final snackBar = SnackBar(
content: Text("$errorData"),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
}
PlacesAutocompleteResponse parseResponse(Map responseBody) {
return PlacesAutocompleteResponse.fromJson(
responseBody as Map<String, dynamic>);
}
PlaceDetails parsePlaceDetailMap(Map responseBody) {
return PlaceDetails.fromJson(responseBody as Map<String, dynamic>);
}
typedef ItemClick = void Function(Prediction postalCodeResponse);
typedef GetPlaceDetailswWithLatLng = void Function(
Prediction postalCodeResponse);
typedef ListItemBuilder = Widget Function(
BuildContext context, int index, Prediction prediction);

View File

@@ -0,0 +1,43 @@
class District {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? provinceCode;
String? type;
District({
this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.provinceCode,
this.type,
});
District.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
provinceCode = json['parent'];
type = json['type'];
}
static List<District> fromJsonList(List list) {
return list.map((e) => District.fromJson(e)).toList();
}
static List<District> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => District.fromJson(e)).toList();
}
// @override
// String toString() => fullName!;
}

View File

@@ -0,0 +1,172 @@
class NearbySearch {
List<Result>? results;
String? status;
NearbySearch({
this.results,
this.status,
});
NearbySearch.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results!.add(Result.fromJson(v));
});
}
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (results != null) {
data['result'] = results!.map((v) => v.toJson()).toList();
}
data['status'] = status;
return data;
}
}
class PlaceDetails {
Result? result;
String? status;
PlaceDetails({this.result, this.status});
PlaceDetails.fromJson(Map<String, dynamic> json) {
result = json['result'] != null ? Result.fromJson(json['result']) : null;
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (result != null) {
data['result'] = result!.toJson();
}
data['status'] = status;
return data;
}
static List<PlaceDetails> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => PlaceDetails.fromJson(e)).toList();
}
}
class Result {
Geometry? geometry;
String? icon;
String? name;
String? placeId;
String? vicinity;
String? adress;
String? formattedAddress;
String? formattedPhoneNumber;
String? phoneNumber;
OpeningHours? openingHours;
Result({
this.geometry,
this.icon,
this.name,
this.placeId,
this.vicinity,
this.formattedPhoneNumber,
this.phoneNumber,
this.openingHours,
this.adress,
this.formattedAddress,
});
Result.fromJson(Map<String, dynamic> json) {
geometry =
json['geometry'] != null ? Geometry.fromJson(json['geometry']) : null;
icon = json['icon'];
name = json['name'];
placeId = json['place_id'];
vicinity = json['vicinity'];
adress = json['adr_address'];
formattedPhoneNumber = json['formatted_phone_number'] ?? "";
phoneNumber = json['international_phone_number'] ?? "";
formattedAddress = json['formatted_address'];
openingHours = json['opening_hours'] != null
? OpeningHours.fromJson(json['opening_hours'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (geometry != null) {
data['geometry'] = geometry!.toJson();
}
data['icon'] = icon;
data['name'] = name;
data['vicinity'] = vicinity;
data['adr_address'] = adress;
data['formatted_address'] = formattedAddress;
data['international_phone_number'] = phoneNumber;
data['formatted_phone_number'] = formattedPhoneNumber;
if (openingHours != null) {
data['opening_hours'] = openingHours!.toJson();
}
return data;
}
}
class Geometry {
Location? location;
Geometry({
this.location,
});
Geometry.fromJson(Map<String, dynamic> json) {
location =
json['location'] != null ? Location.fromJson(json['location']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (location != null) {
data['location'] = location!.toJson();
}
return data;
}
}
class Location {
double? lat;
double? lng;
Location({
this.lat,
this.lng,
});
Location.fromJson(Map<String, dynamic> json) {
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class OpeningHours {
bool? openNow;
OpeningHours({
this.openNow,
});
OpeningHours.fromJson(Map<String, dynamic> json) {
openNow = json['open_now'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['open_now'] = openNow;
return data;
}
}

View File

@@ -0,0 +1,42 @@
class Province {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? parent;
String? type;
Province(
{this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.parent,
this.type});
Province.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
parent = json['parent'];
type = json['type'];
}
static List<Province> fromJsonList(List list) {
return list.map((e) => Province.fromJson(e)).toList();
}
static List<Province> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => Province.fromJson(e)).toList();
}
@override
String toString() => fullName!;
}

View File

@@ -0,0 +1,43 @@
class Ward {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? districtCode;
String? type;
Ward({
this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.districtCode,
this.type,
});
Ward.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
districtCode = json['parent'];
type = json['type'];
}
static List<Ward> fromJsonList(List list) {
return list.map((e) => Ward.fromJson(e)).toList();
}
static List<Ward> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => Ward.fromJson(e)).toList();
}
@override
String toString() => fullName!;
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
class SharedBackground extends StatelessWidget {
final Widget child;
const SharedBackground({super.key, required this.child});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Directionality(
textDirection: TextDirection.ltr,
child: Scaffold(
resizeToAvoidBottomInset: false,
body: SizedBox(
height: size.height,
width: double.infinity,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: Image.asset(
ImageConstants.instance.getImage("background_top"),
width: size.width * 0.3,
),
),
Positioned(
bottom: 0,
right: 0,
child: Image.asset(
ImageConstants.instance.getImage("background_bottom"),
width: size.width * 0.4,
),
),
SafeArea(child: child)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import '../extention/context_extention.dart';
InputDecoration borderRadiusTopLeftAndBottomRight(
BuildContext context, String hintText) =>
InputDecoration(
hintText: hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12), bottomRight: Radius.circular(12)),
));
InputDecoration borderRadiusAll(BuildContext context, String hintText) =>
InputDecoration(
hintText: hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(context.normalRadius),
));

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/extention/context_extention.dart';
import 'package:top_snackbar_flutter/custom_snack_bar.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart';
void showNoIconTopSnackBar(BuildContext context, String message,
Color backgroundColor, Color textColor) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
Card(
color: backgroundColor,
child: Container(
margin: const EdgeInsets.only(left: 20),
height: 50,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
message,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: textColor,
),
),
),
),
),
displayDuration: context.lowDuration);
}
void showSuccessTopSnackBarCustom(BuildContext context, String message) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
SizedBox(
height: 60,
child: CustomSnackBar.success(
message: message,
icon: const Icon(Icons.sentiment_very_satisfied_outlined,
color: Color(0x15000000), size: 80))),
displayDuration: context.lowDuration);
}
void showErrorTopSnackBarCustom(BuildContext context, String message) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
SizedBox(
height: 60,
child: CustomSnackBar.error(
message: message,
icon: const Icon(Icons.sentiment_dissatisfied_outlined,
color: Color(0x15000000), size: 80))),
displayDuration: context.lowDuration);
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
/// Hiệu ứng di chuyển từ phải qua trái
Widget transitionsRightToLeft(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ trái sang phải
Widget transitionsLeftToRight(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(-1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ dưới lên trên
Widget transitionsBottomToTop(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ trên xuống dưới
Widget transitionsTopToBottom(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(0.0, -1.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng mở dần vào và ra
Widget transitionsFaded(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0;
const end = 1.0;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var fadeAnimation = animation.drive(tween);
return FadeTransition(
opacity: fadeAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ kích thước nhỏ đến đầy đủ
Widget transitionsScale(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0; // Bắt đầu từ kích thước 0
const end = 1.0; // Kết thúc ở kích thước 1
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var scaleAnimation = animation.drive(tween);
return ScaleTransition(
scale: scaleAnimation,
child: child,
);
}
Widget transitionsTotation(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0; // Bắt đầu từ góc 0 độ
const end = 1.0; // Kết thúc ở góc 1 vòng (360 độ)
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var rotationAnimation = animation.drive(tween);
return RotationTransition(
turns: rotationAnimation,
child: child,
);
}
/// Hiệu ứng kết hợp (Di chuyển và mờ dần)
Widget transitionsCustom1(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const beginOffset = Offset(1.0, 0.0); // Di chuyển từ phải vào
const endOffset = Offset.zero;
const beginOpacity = 0.0; // Bắt đầu từ độ mờ 0
const endOpacity = 1.0; // Kết thúc ở độ mờ 1
var offsetTween = Tween(begin: beginOffset, end: endOffset);
var opacityTween = Tween(begin: beginOpacity, end: endOpacity);
var offsetAnimation =
animation.drive(offsetTween.chain(CurveTween(curve: Curves.easeInOut)));
var opacityAnimation =
animation.drive(opacityTween.chain(CurveTween(curve: Curves.easeInOut)));
return FadeTransition(
opacity: opacityAnimation,
child: SlideTransition(
position: offsetAnimation,
child: child,
),
);
}