Complete refactoring SFM App Source Code
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
43
lib/product/shared/model/district_model.dart
Normal file
43
lib/product/shared/model/district_model.dart
Normal 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!;
|
||||
}
|
||||
172
lib/product/shared/model/near_by_search_model.dart
Normal file
172
lib/product/shared/model/near_by_search_model.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
42
lib/product/shared/model/province_model.dart
Normal file
42
lib/product/shared/model/province_model.dart
Normal 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!;
|
||||
}
|
||||
43
lib/product/shared/model/ward_model.dart
Normal file
43
lib/product/shared/model/ward_model.dart
Normal 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!;
|
||||
}
|
||||
44
lib/product/shared/shared_background.dart
Normal file
44
lib/product/shared/shared_background.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/product/shared/shared_input_decoration.dart
Normal file
18
lib/product/shared/shared_input_decoration.dart
Normal 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),
|
||||
));
|
||||
56
lib/product/shared/shared_snack_bar.dart
Normal file
56
lib/product/shared/shared_snack_bar.dart
Normal 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);
|
||||
}
|
||||
137
lib/product/shared/shared_transition.dart
Normal file
137
lib/product/shared/shared_transition.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user