Packages And offers..
parent
f410ce20ca
commit
6f2c947687
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,34 @@
|
||||
enum PaymentOptions {
|
||||
VISA,
|
||||
MASTERCARD,
|
||||
MADA,
|
||||
INSTALLMENT,
|
||||
APPLEPAY
|
||||
}
|
||||
|
||||
extension PaymentOptions_ on PaymentOptions{
|
||||
String value(){
|
||||
switch(this){
|
||||
case PaymentOptions.VISA:
|
||||
return "VISA";
|
||||
break;
|
||||
|
||||
case PaymentOptions.MASTERCARD:
|
||||
return "MASTERCARD";
|
||||
break;
|
||||
|
||||
case PaymentOptions.MADA:
|
||||
return "MADA";
|
||||
break;
|
||||
|
||||
case PaymentOptions.INSTALLMENT:
|
||||
return "INSTALLMENT";
|
||||
break;
|
||||
case PaymentOptions.APPLEPAY:
|
||||
return "APPLEPAY";
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
class ResponseModel<T>{
|
||||
final bool status;
|
||||
final String error;
|
||||
final T data;
|
||||
|
||||
ResponseModel({this.status, this.data, this.error});
|
||||
}
|
||||
@ -0,0 +1,323 @@
|
||||
|
||||
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart';
|
||||
import 'PackagesCartItemsResponseModel.dart';
|
||||
|
||||
class PackagesOrderResponseModel {
|
||||
String _customOrderNumber;
|
||||
int _storeId;
|
||||
dynamic _pickUpInStore;
|
||||
String _paymentMethodSystemName;
|
||||
String _customerCurrencyCode;
|
||||
double _currencyRate;
|
||||
int _customerTaxDisplayTypeId;
|
||||
dynamic _vatNumber;
|
||||
double _orderSubtotalInclTax;
|
||||
double _orderSubtotalExclTax;
|
||||
double _orderSubTotalDiscountInclTax;
|
||||
double _orderSubTotalDiscountExclTax;
|
||||
double _orderShippingInclTax;
|
||||
double _orderShippingExclTax;
|
||||
double _paymentMethodAdditionalFeeInclTax;
|
||||
double _paymentMethodAdditionalFeeExclTax;
|
||||
String _taxRates;
|
||||
double _orderTax;
|
||||
double _orderDiscount;
|
||||
double _orderTotal;
|
||||
double _refundedAmount;
|
||||
dynamic _rewardPointsWereAdded;
|
||||
String _checkoutAttributeDescription;
|
||||
int _customerLanguageId;
|
||||
int _affiliateId;
|
||||
String _customerIp;
|
||||
dynamic _authorizationTransactionId;
|
||||
dynamic _authorizationTransactionCode;
|
||||
dynamic _authorizationTransactionResult;
|
||||
dynamic _captureTransactionId;
|
||||
dynamic _captureTransactionResult;
|
||||
dynamic _subscriptionTransactionId;
|
||||
dynamic _paidDateUtc;
|
||||
dynamic _shippingMethod;
|
||||
dynamic _shippingRateComputationMethodSystemName;
|
||||
String _customValuesXml;
|
||||
dynamic _paymentOption;
|
||||
bool _deleted;
|
||||
String _createdOnUtc;
|
||||
PackagesCustomerResponseModel _customer;
|
||||
int _customerId;
|
||||
dynamic _billingAddress;
|
||||
dynamic _shippingAddress;
|
||||
List<PackagesCartItemsResponseModel> _orderItems;
|
||||
String _orderStatus;
|
||||
String _paymentStatus;
|
||||
String _shippingStatus;
|
||||
String _customerTaxDisplayType;
|
||||
int _id;
|
||||
|
||||
String get customOrderNumber => _customOrderNumber;
|
||||
int get storeId => _storeId;
|
||||
dynamic get pickUpInStore => _pickUpInStore;
|
||||
String get paymentMethodSystemName => _paymentMethodSystemName;
|
||||
String get customerCurrencyCode => _customerCurrencyCode;
|
||||
double get currencyRate => _currencyRate;
|
||||
int get customerTaxDisplayTypeId => _customerTaxDisplayTypeId;
|
||||
dynamic get vatNumber => _vatNumber;
|
||||
double get orderSubtotalInclTax => _orderSubtotalInclTax;
|
||||
double get orderSubtotalExclTax => _orderSubtotalExclTax;
|
||||
double get orderSubTotalDiscountInclTax => _orderSubTotalDiscountInclTax;
|
||||
double get orderSubTotalDiscountExclTax => _orderSubTotalDiscountExclTax;
|
||||
double get orderShippingInclTax => _orderShippingInclTax;
|
||||
double get orderShippingExclTax => _orderShippingExclTax;
|
||||
double get paymentMethodAdditionalFeeInclTax => _paymentMethodAdditionalFeeInclTax;
|
||||
double get paymentMethodAdditionalFeeExclTax => _paymentMethodAdditionalFeeExclTax;
|
||||
String get taxRates => _taxRates;
|
||||
double get orderTax => _orderTax;
|
||||
double get orderDiscount => _orderDiscount;
|
||||
double get orderTotal => _orderTotal;
|
||||
double get refundedAmount => _refundedAmount;
|
||||
dynamic get rewardPointsWereAdded => _rewardPointsWereAdded;
|
||||
String get checkoutAttributeDescription => _checkoutAttributeDescription;
|
||||
int get customerLanguageId => _customerLanguageId;
|
||||
int get affiliateId => _affiliateId;
|
||||
String get customerIp => _customerIp;
|
||||
dynamic get authorizationTransactionId => _authorizationTransactionId;
|
||||
dynamic get authorizationTransactionCode => _authorizationTransactionCode;
|
||||
dynamic get authorizationTransactionResult => _authorizationTransactionResult;
|
||||
dynamic get captureTransactionId => _captureTransactionId;
|
||||
dynamic get captureTransactionResult => _captureTransactionResult;
|
||||
dynamic get subscriptionTransactionId => _subscriptionTransactionId;
|
||||
dynamic get paidDateUtc => _paidDateUtc;
|
||||
dynamic get shippingMethod => _shippingMethod;
|
||||
dynamic get shippingRateComputationMethodSystemName => _shippingRateComputationMethodSystemName;
|
||||
String get customValuesXml => _customValuesXml;
|
||||
dynamic get paymentOption => _paymentOption;
|
||||
bool get deleted => _deleted;
|
||||
String get createdOnUtc => _createdOnUtc;
|
||||
PackagesCustomerResponseModel get customer => _customer;
|
||||
int get customerId => _customerId;
|
||||
dynamic get billingAddress => _billingAddress;
|
||||
dynamic get shippingAddress => _shippingAddress;
|
||||
List<PackagesCartItemsResponseModel> get orderItems => _orderItems;
|
||||
String get orderStatus => _orderStatus;
|
||||
String get paymentStatus => _paymentStatus;
|
||||
String get shippingStatus => _shippingStatus;
|
||||
String get customerTaxDisplayType => _customerTaxDisplayType;
|
||||
int get id => _id;
|
||||
|
||||
OrderResponseModel({
|
||||
String customOrderNumber,
|
||||
int storeId,
|
||||
dynamic pickUpInStore,
|
||||
String paymentMethodSystemName,
|
||||
String customerCurrencyCode,
|
||||
double currencyRate,
|
||||
int customerTaxDisplayTypeId,
|
||||
dynamic vatNumber,
|
||||
double orderSubtotalInclTax,
|
||||
double orderSubtotalExclTax,
|
||||
double orderSubTotalDiscountInclTax,
|
||||
double orderSubTotalDiscountExclTax,
|
||||
double orderShippingInclTax,
|
||||
double orderShippingExclTax,
|
||||
double paymentMethodAdditionalFeeInclTax,
|
||||
double paymentMethodAdditionalFeeExclTax,
|
||||
String taxRates,
|
||||
double orderTax,
|
||||
double orderDiscount,
|
||||
double orderTotal,
|
||||
double refundedAmount,
|
||||
dynamic rewardPointsWereAdded,
|
||||
String checkoutAttributeDescription,
|
||||
int customerLanguageId,
|
||||
int affiliateId,
|
||||
String customerIp,
|
||||
dynamic authorizationTransactionId,
|
||||
dynamic authorizationTransactionCode,
|
||||
dynamic authorizationTransactionResult,
|
||||
dynamic captureTransactionId,
|
||||
dynamic captureTransactionResult,
|
||||
dynamic subscriptionTransactionId,
|
||||
dynamic paidDateUtc,
|
||||
dynamic shippingMethod,
|
||||
dynamic shippingRateComputationMethodSystemName,
|
||||
String customValuesXml,
|
||||
dynamic paymentOption,
|
||||
bool deleted,
|
||||
String createdOnUtc,
|
||||
PackagesCustomerResponseModel customer,
|
||||
int customerId,
|
||||
dynamic billingAddress,
|
||||
dynamic shippingAddress,
|
||||
List<PackagesCartItemsResponseModel> orderItems,
|
||||
String orderStatus,
|
||||
String paymentStatus,
|
||||
String shippingStatus,
|
||||
String customerTaxDisplayType,
|
||||
int id}){
|
||||
_customOrderNumber = customOrderNumber;
|
||||
_storeId = storeId;
|
||||
_pickUpInStore = pickUpInStore;
|
||||
_paymentMethodSystemName = paymentMethodSystemName;
|
||||
_customerCurrencyCode = customerCurrencyCode;
|
||||
_currencyRate = currencyRate;
|
||||
_customerTaxDisplayTypeId = customerTaxDisplayTypeId;
|
||||
_vatNumber = vatNumber;
|
||||
_orderSubtotalInclTax = orderSubtotalInclTax;
|
||||
_orderSubtotalExclTax = orderSubtotalExclTax;
|
||||
_orderSubTotalDiscountInclTax = orderSubTotalDiscountInclTax;
|
||||
_orderSubTotalDiscountExclTax = orderSubTotalDiscountExclTax;
|
||||
_orderShippingInclTax = orderShippingInclTax;
|
||||
_orderShippingExclTax = orderShippingExclTax;
|
||||
_paymentMethodAdditionalFeeInclTax = paymentMethodAdditionalFeeInclTax;
|
||||
_paymentMethodAdditionalFeeExclTax = paymentMethodAdditionalFeeExclTax;
|
||||
_taxRates = taxRates;
|
||||
_orderTax = orderTax;
|
||||
_orderDiscount = orderDiscount;
|
||||
_orderTotal = orderTotal;
|
||||
_refundedAmount = refundedAmount;
|
||||
_rewardPointsWereAdded = rewardPointsWereAdded;
|
||||
_checkoutAttributeDescription = checkoutAttributeDescription;
|
||||
_customerLanguageId = customerLanguageId;
|
||||
_affiliateId = affiliateId;
|
||||
_customerIp = customerIp;
|
||||
_authorizationTransactionId = authorizationTransactionId;
|
||||
_authorizationTransactionCode = authorizationTransactionCode;
|
||||
_authorizationTransactionResult = authorizationTransactionResult;
|
||||
_captureTransactionId = captureTransactionId;
|
||||
_captureTransactionResult = captureTransactionResult;
|
||||
_subscriptionTransactionId = subscriptionTransactionId;
|
||||
_paidDateUtc = paidDateUtc;
|
||||
_shippingMethod = shippingMethod;
|
||||
_shippingRateComputationMethodSystemName = shippingRateComputationMethodSystemName;
|
||||
_customValuesXml = customValuesXml;
|
||||
_paymentOption = paymentOption;
|
||||
_deleted = deleted;
|
||||
_createdOnUtc = createdOnUtc;
|
||||
_customer = customer;
|
||||
_customerId = customerId;
|
||||
_billingAddress = billingAddress;
|
||||
_shippingAddress = shippingAddress;
|
||||
_orderItems = orderItems;
|
||||
_orderStatus = orderStatus;
|
||||
_paymentStatus = paymentStatus;
|
||||
_shippingStatus = shippingStatus;
|
||||
_customerTaxDisplayType = customerTaxDisplayType;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
PackagesOrderResponseModel.fromJson(dynamic json) {
|
||||
_customOrderNumber = json["custom_order_number"];
|
||||
_storeId = json["store_id"];
|
||||
_pickUpInStore = json["pick_up_in_store"];
|
||||
_paymentMethodSystemName = json["payment_method_system_name"];
|
||||
_customerCurrencyCode = json["customer_currency_code"];
|
||||
_currencyRate = json["currency_rate"];
|
||||
_customerTaxDisplayTypeId = json["customer_tax_display_type_id"];
|
||||
_vatNumber = json["vat_number"];
|
||||
_orderSubtotalInclTax = json["order_subtotal_incl_tax"];
|
||||
_orderSubtotalExclTax = json["order_subtotal_excl_tax"];
|
||||
_orderSubTotalDiscountInclTax = json["order_sub_total_discount_incl_tax"];
|
||||
_orderSubTotalDiscountExclTax = json["order_sub_total_discount_excl_tax"];
|
||||
_orderShippingInclTax = json["order_shipping_incl_tax"];
|
||||
_orderShippingExclTax = json["order_shipping_excl_tax"];
|
||||
_paymentMethodAdditionalFeeInclTax = json["payment_method_additional_fee_incl_tax"];
|
||||
_paymentMethodAdditionalFeeExclTax = json["payment_method_additional_fee_excl_tax"];
|
||||
_taxRates = json["tax_rates"];
|
||||
_orderTax = json["order_tax"];
|
||||
_orderDiscount = json["order_discount"];
|
||||
_orderTotal = json["order_total"];
|
||||
_refundedAmount = json["refunded_amount"];
|
||||
_rewardPointsWereAdded = json["reward_points_were_added"];
|
||||
_checkoutAttributeDescription = json["checkout_attribute_description"];
|
||||
_customerLanguageId = json["customer_language_id"];
|
||||
_affiliateId = json["affiliate_id"];
|
||||
_customerIp = json["customer_ip"];
|
||||
_authorizationTransactionId = json["authorization_transaction_id"];
|
||||
_authorizationTransactionCode = json["authorization_transaction_code"];
|
||||
_authorizationTransactionResult = json["authorization_transaction_result"];
|
||||
_captureTransactionId = json["capture_transaction_id"];
|
||||
_captureTransactionResult = json["capture_transaction_result"];
|
||||
_subscriptionTransactionId = json["subscription_transaction_id"];
|
||||
_paidDateUtc = json["paid_date_utc"];
|
||||
_shippingMethod = json["shipping_method"];
|
||||
_shippingRateComputationMethodSystemName = json["shipping_rate_computation_method_system_name"];
|
||||
_customValuesXml = json["custom_values_xml"];
|
||||
_paymentOption = json["payment_option"];
|
||||
_deleted = json["deleted"];
|
||||
_createdOnUtc = json["created_on_utc"];
|
||||
_customer = json["customer"] != null ? PackagesCustomerResponseModel.fromJson(json["customer"]) : null;
|
||||
_customerId = json["customer_id"];
|
||||
_billingAddress = json["billing_address"];
|
||||
_shippingAddress = json["shipping_address"];
|
||||
if (json["order_items"] != null) {
|
||||
_orderItems = [];
|
||||
json["order_items"].forEach((v) {
|
||||
_orderItems.add(PackagesCartItemsResponseModel.fromJson(v));
|
||||
});
|
||||
}
|
||||
_orderStatus = json["order_status"];
|
||||
_paymentStatus = json["payment_status"];
|
||||
_shippingStatus = json["shipping_status"];
|
||||
_customerTaxDisplayType = json["customer_tax_display_type"];
|
||||
_id = json["id"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
var map = <String, dynamic>{};
|
||||
map["custom_order_number"] = _customOrderNumber;
|
||||
map["store_id"] = _storeId;
|
||||
map["pick_up_in_store"] = _pickUpInStore;
|
||||
map["payment_method_system_name"] = _paymentMethodSystemName;
|
||||
map["customer_currency_code"] = _customerCurrencyCode;
|
||||
map["currency_rate"] = _currencyRate;
|
||||
map["customer_tax_display_type_id"] = _customerTaxDisplayTypeId;
|
||||
map["vat_number"] = _vatNumber;
|
||||
map["order_subtotal_incl_tax"] = _orderSubtotalInclTax;
|
||||
map["order_subtotal_excl_tax"] = _orderSubtotalExclTax;
|
||||
map["order_sub_total_discount_incl_tax"] = _orderSubTotalDiscountInclTax;
|
||||
map["order_sub_total_discount_excl_tax"] = _orderSubTotalDiscountExclTax;
|
||||
map["order_shipping_incl_tax"] = _orderShippingInclTax;
|
||||
map["order_shipping_excl_tax"] = _orderShippingExclTax;
|
||||
map["payment_method_additional_fee_incl_tax"] = _paymentMethodAdditionalFeeInclTax;
|
||||
map["payment_method_additional_fee_excl_tax"] = _paymentMethodAdditionalFeeExclTax;
|
||||
map["tax_rates"] = _taxRates;
|
||||
map["order_tax"] = _orderTax;
|
||||
map["order_discount"] = _orderDiscount;
|
||||
map["order_total"] = _orderTotal;
|
||||
map["refunded_amount"] = _refundedAmount;
|
||||
map["reward_points_were_added"] = _rewardPointsWereAdded;
|
||||
map["checkout_attribute_description"] = _checkoutAttributeDescription;
|
||||
map["customer_language_id"] = _customerLanguageId;
|
||||
map["affiliate_id"] = _affiliateId;
|
||||
map["customer_ip"] = _customerIp;
|
||||
map["authorization_transaction_id"] = _authorizationTransactionId;
|
||||
map["authorization_transaction_code"] = _authorizationTransactionCode;
|
||||
map["authorization_transaction_result"] = _authorizationTransactionResult;
|
||||
map["capture_transaction_id"] = _captureTransactionId;
|
||||
map["capture_transaction_result"] = _captureTransactionResult;
|
||||
map["subscription_transaction_id"] = _subscriptionTransactionId;
|
||||
map["paid_date_utc"] = _paidDateUtc;
|
||||
map["shipping_method"] = _shippingMethod;
|
||||
map["shipping_rate_computation_method_system_name"] = _shippingRateComputationMethodSystemName;
|
||||
map["custom_values_xml"] = _customValuesXml;
|
||||
map["payment_option"] = _paymentOption;
|
||||
map["deleted"] = _deleted;
|
||||
map["created_on_utc"] = _createdOnUtc;
|
||||
if (_customer != null) {
|
||||
map["customer"] = _customer.toJson();
|
||||
}
|
||||
map["customer_id"] = _customerId;
|
||||
map["billing_address"] = _billingAddress;
|
||||
map["shipping_address"] = _shippingAddress;
|
||||
if (_orderItems != null) {
|
||||
map["order_items"] = _orderItems.map((v) => v.toJson()).toList();
|
||||
}
|
||||
map["order_status"] = _orderStatus;
|
||||
map["payment_status"] = _paymentStatus;
|
||||
map["shipping_status"] = _shippingStatus;
|
||||
map["customer_tax_display_type"] = _customerTaxDisplayType;
|
||||
map["id"] = _id;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
import 'package:after_layout/after_layout.dart';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:diplomaticquarterapp/config/size_config.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart';
|
||||
import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart';
|
||||
import 'package:diplomaticquarterapp/locator.dart';
|
||||
import 'package:diplomaticquarterapp/pages/base/base_view.dart';
|
||||
import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart';
|
||||
import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart';
|
||||
import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart';
|
||||
import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart';
|
||||
import 'package:diplomaticquarterapp/uitl/utils.dart' as utils;
|
||||
import 'package:diplomaticquarterapp/widgets/AnimatedTextFields.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/LoadingButton.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/carousel_indicator/carousel_indicator.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart';
|
||||
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
|
||||
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_material_pickers/flutter_material_pickers.dart';
|
||||
|
||||
dynamic languageID;
|
||||
var emailRegex = RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$');
|
||||
|
||||
class CreateCustomerDialogPage extends StatefulWidget {
|
||||
final BuildContext context;
|
||||
CreateCustomerDialogPage({this.context});
|
||||
PackagesViewModel viewModel;
|
||||
|
||||
Future<PackagesCustomerResponseModel> show() async{
|
||||
await showDialog(context: context, builder: (context ){
|
||||
return AlertDialog(content: this, shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20)
|
||||
), elevation: 5, );
|
||||
});
|
||||
return viewModel.service.customer;
|
||||
}
|
||||
|
||||
@override
|
||||
_CreateCustomerDialogPageState createState() => _CreateCustomerDialogPageState();
|
||||
|
||||
}
|
||||
|
||||
class _CreateCustomerDialogPageState extends State<CreateCustomerDialogPage> with AfterLayoutMixin<CreateCustomerDialogPage>, TickerProviderStateMixin{
|
||||
AnimationController _loadingController;
|
||||
AnimationController _submitController;
|
||||
bool _enableInput = true;
|
||||
|
||||
Interval _nameTextFieldLoadingAnimationInterval = const Interval(0, .85);
|
||||
|
||||
final _phoneFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_submitController = AnimationController(vsync: this, duration: Duration(milliseconds: 1000),);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void afterFirstLayout(BuildContext context) async{
|
||||
}
|
||||
|
||||
// Controllers
|
||||
TextEditingController _emailTextController = TextEditingController();
|
||||
TextEditingController _phoneTextController = TextEditingController();
|
||||
TextEditingController _emailPinTextController = TextEditingController();
|
||||
TextEditingController _phonePinTextController = TextEditingController();
|
||||
|
||||
bool verifyPin = false;
|
||||
|
||||
PackagesViewModel viewModel() => widget.viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return BaseView<PackagesViewModel>(
|
||||
allowAny: true,
|
||||
onModelReady: (model) => widget.viewModel = model,
|
||||
builder: (_, model, wi) => verifyPin ? verifyPinWidget() : userDetailWidget()
|
||||
);
|
||||
}
|
||||
|
||||
Widget verifyPinWidget(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
Widget userDetailWidget(){
|
||||
return
|
||||
Container(
|
||||
width: SizeConfig.realScreenWidth * 0.8,
|
||||
height: 270,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text("Create Guest Customer"),
|
||||
SizedBox(height: 30,),
|
||||
AnimatedTextFormField(
|
||||
enabled: _enableInput,
|
||||
controller: _emailTextController,
|
||||
width: 100,
|
||||
loadingController: _loadingController,
|
||||
interval: _nameTextFieldLoadingAnimationInterval,
|
||||
labelText: "Email",
|
||||
prefixIcon: Icon(Icons.email),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
textInputAction: TextInputAction.next,
|
||||
onFieldSubmitted: (value) {
|
||||
FocusScope.of(context).requestFocus(_phoneFocusNode);
|
||||
},
|
||||
validator: (value){
|
||||
return (value.isEmpty || !emailRegex.hasMatch(value))
|
||||
? 'Invalid email!'
|
||||
: null;
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 30,),
|
||||
AnimatedTextFormField(
|
||||
enabled: _enableInput,
|
||||
controller: _phoneTextController,
|
||||
width: 100,
|
||||
loadingController: _loadingController,
|
||||
interval: _nameTextFieldLoadingAnimationInterval,
|
||||
labelText: "Mobile Number",
|
||||
prefixIcon: Icon(Icons.phone_android),
|
||||
keyboardType: TextInputType.phone,
|
||||
textInputAction: TextInputAction.next,
|
||||
onFieldSubmitted: (value) {
|
||||
FocusScope.of(context).requestFocus(_phoneFocusNode);
|
||||
},
|
||||
validator: (value){
|
||||
return (value.isEmpty || !emailRegex.hasMatch(value))
|
||||
? 'Invalid email!'
|
||||
: null;
|
||||
},
|
||||
),
|
||||
Spacer(flex: 1,),
|
||||
|
||||
AnimatedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
loadingColor: Theme.of(context).primaryColor,
|
||||
controller: _submitController,
|
||||
text: TranslationBase.of(context).done,
|
||||
onPressed: (){
|
||||
createCustomer();
|
||||
},
|
||||
)
|
||||
|
||||
// RaisedButton(
|
||||
// child: Text(
|
||||
// TranslationBase.of(context).done,
|
||||
// style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// padding: EdgeInsets.only(top: 5, bottom: 5, left: 0, right: 0),
|
||||
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5), side: BorderSide(color: Theme.of(context).primaryColor, width: 0.5)),
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// onPressed: (){
|
||||
//
|
||||
// },
|
||||
// )
|
||||
,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
createCustomer() async{
|
||||
setState(() => _enableInput = false);
|
||||
|
||||
loading(true);
|
||||
var request = PackagesCustomerRequestModel(email: _emailTextController.text, phoneNumber: _phoneTextController.text);
|
||||
viewModel().service
|
||||
.createCustomer(request, context: context, showLoading: false)
|
||||
.then((value) => success())
|
||||
.catchError((error) => showError(error));
|
||||
|
||||
}
|
||||
|
||||
success() async{
|
||||
loading(false);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
showError(String errorMessage) async{
|
||||
loading(false);
|
||||
setState(() => _enableInput = true);
|
||||
}
|
||||
|
||||
loading(bool can){
|
||||
can ? _submitController.forward() : _submitController.reverse();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart';
|
||||
import 'package:diplomaticquarterapp/pages/base/base_view.dart';
|
||||
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
dynamic languageID;
|
||||
|
||||
class PackageOrderCompletedPage extends StatelessWidget{
|
||||
double buttonHeight;
|
||||
double buttonWidth;
|
||||
Widget icon;
|
||||
String heading;
|
||||
String title;
|
||||
String subTitle;
|
||||
String actionTitle;
|
||||
|
||||
PackageOrderCompletedPage({this.buttonWidth, this.buttonHeight, @required this.heading, @required this.title, @required this.subTitle, this.actionTitle });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert((heading != null || title != null || subTitle != null), "Data missing in properties");
|
||||
|
||||
buttonWidth = buttonWidth ?? MediaQuery.of(context).size.width/2;
|
||||
buttonHeight = buttonHeight ?? 40;
|
||||
actionTitle = actionTitle ?? TranslationBase.of(context).done;
|
||||
|
||||
return BaseView<PackagesViewModel>(
|
||||
allowAny: true,
|
||||
onModelReady: (model){},
|
||||
builder: (_, model, wi){
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
AspectRatio(
|
||||
aspectRatio: 1.2/1,
|
||||
child:
|
||||
iconWidget(context),
|
||||
),
|
||||
|
||||
headingWidget(context),
|
||||
|
||||
|
||||
AspectRatio(
|
||||
aspectRatio: 1/1,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
titleWidget(context),
|
||||
SizedBox(height: 20,),
|
||||
subTitleWidget(context),
|
||||
SizedBox(height: 50,),
|
||||
actionWidget(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget iconWidget(BuildContext context){
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(50),
|
||||
child: icon ?? SvgPicture.asset(
|
||||
"assets/images/svg/success.svg",
|
||||
semanticsLabel: 'icon'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget headingWidget(BuildContext context) => Text(
|
||||
heading,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontSize: 35.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.9
|
||||
)
|
||||
);
|
||||
|
||||
Widget titleWidget(BuildContext context) => Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontSize: 25.0,
|
||||
fontWeight: FontWeight.w200,
|
||||
letterSpacing: 0.9
|
||||
)
|
||||
);
|
||||
|
||||
Widget subTitleWidget(BuildContext context) => Text(
|
||||
subTitle,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 0.9
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Widget actionWidget(BuildContext context) => Container(
|
||||
height: buttonHeight,
|
||||
width: buttonWidth,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
||||
shape:RoundedRectangleBorder(
|
||||
borderRadius: new BorderRadius.circular(buttonHeight/2),
|
||||
)
|
||||
),
|
||||
child: Text(
|
||||
actionTitle,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
)
|
||||
),
|
||||
onPressed: (){
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
@ -0,0 +1,347 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum TextFieldInertiaDirection {
|
||||
left,
|
||||
right,
|
||||
}
|
||||
|
||||
Interval _getInternalInterval(
|
||||
double start,
|
||||
double end,
|
||||
double externalStart,
|
||||
double externalEnd, [
|
||||
Curve curve = Curves.linear,
|
||||
]) {
|
||||
return Interval(
|
||||
start + (end - start) * externalStart,
|
||||
start + (end - start) * externalEnd,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
class AnimatedTextFormField extends StatefulWidget {
|
||||
AnimatedTextFormField({
|
||||
Key key,
|
||||
this.interval = const Interval(0.0, 1.0),
|
||||
@required this.width,
|
||||
this.loadingController,
|
||||
this.inertiaController,
|
||||
this.inertiaDirection,
|
||||
this.enabled = true,
|
||||
this.labelText,
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.obscureText = false,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.validator,
|
||||
this.onFieldSubmitted,
|
||||
this.onSaved,
|
||||
}) : assert((inertiaController == null && inertiaDirection == null) ||
|
||||
(inertiaController != null && inertiaDirection != null)),
|
||||
super(key: key);
|
||||
|
||||
final Interval interval;
|
||||
final AnimationController loadingController;
|
||||
final AnimationController inertiaController;
|
||||
final double width;
|
||||
final bool enabled;
|
||||
final String labelText;
|
||||
final Widget prefixIcon;
|
||||
final Widget suffixIcon;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction textInputAction;
|
||||
final bool obscureText;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final FormFieldValidator<String> validator;
|
||||
final ValueChanged<String> onFieldSubmitted;
|
||||
final FormFieldSetter<String> onSaved;
|
||||
final TextFieldInertiaDirection inertiaDirection;
|
||||
|
||||
@override
|
||||
_AnimatedTextFormFieldState createState() => _AnimatedTextFormFieldState();
|
||||
}
|
||||
|
||||
class _AnimatedTextFormFieldState extends State<AnimatedTextFormField> {
|
||||
Animation<double> scaleAnimation;
|
||||
Animation<double> sizeAnimation;
|
||||
Animation<double> suffixIconOpacityAnimation;
|
||||
|
||||
Animation<double> fieldTranslateAnimation;
|
||||
Animation<double> iconRotationAnimation;
|
||||
Animation<double> iconTranslateAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.inertiaController?.addStatusListener(handleAnimationStatus);
|
||||
|
||||
final interval = widget.interval;
|
||||
final loadingController = widget.loadingController;
|
||||
|
||||
if (loadingController != null) {
|
||||
scaleAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(
|
||||
0, .2, interval.begin, interval.end, Curves.easeOutBack),
|
||||
));
|
||||
suffixIconOpacityAnimation =
|
||||
Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(.65, 1.0, interval.begin, interval.end),
|
||||
));
|
||||
_updateSizeAnimation();
|
||||
}
|
||||
|
||||
final inertiaController = widget.inertiaController;
|
||||
final inertiaDirection = widget.inertiaDirection;
|
||||
final sign = inertiaDirection == TextFieldInertiaDirection.right ? 1 : -1;
|
||||
|
||||
if (inertiaController != null) {
|
||||
fieldTranslateAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: sign * 15.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(0, .5, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
iconRotationAnimation =
|
||||
Tween<double>(begin: 0.0, end: sign * pi / 12 /* ~15deg */)
|
||||
.animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(.5, 1.0, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
iconTranslateAnimation =
|
||||
Tween<double>(begin: 0.0, end: 8.0).animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(.5, 1.0, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSizeAnimation() {
|
||||
final interval = widget.interval;
|
||||
final loadingController = widget.loadingController;
|
||||
|
||||
sizeAnimation = Tween<double>(
|
||||
begin: 48.0,
|
||||
end: widget.width,
|
||||
).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(
|
||||
.2, 1.0, interval.begin, interval.end, Curves.linearToEaseOut),
|
||||
reverseCurve: Curves.easeInExpo,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedTextFormField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.width != widget.width) {
|
||||
_updateSizeAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
widget.inertiaController?.removeStatusListener(handleAnimationStatus);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void handleAnimationStatus(status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
widget.inertiaController?.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildInertiaAnimation(Widget child) {
|
||||
if (widget.inertiaController == null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: iconTranslateAnimation,
|
||||
builder: (context, child) => Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..translate(iconTranslateAnimation.value)
|
||||
..rotateZ(iconRotationAnimation.value),
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _getInputDecoration(ThemeData theme) {
|
||||
return InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: new BorderRadius.circular(10.0),
|
||||
borderSide: new BorderSide(),
|
||||
),
|
||||
labelText: widget.labelText,
|
||||
prefixIcon: _buildInertiaAnimation(widget.prefixIcon),
|
||||
suffixIcon: _buildInertiaAnimation(widget.loadingController != null
|
||||
? FadeTransition(
|
||||
opacity: suffixIconOpacityAnimation,
|
||||
child: widget.suffixIcon,
|
||||
)
|
||||
: widget.suffixIcon),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
Widget textField = TextFormField(
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
decoration: _getInputDecoration(theme),
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.textInputAction,
|
||||
obscureText: widget.obscureText,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
onSaved: widget.onSaved,
|
||||
validator: widget.validator,
|
||||
enabled: widget.enabled,
|
||||
);
|
||||
|
||||
if (widget.loadingController != null) {
|
||||
textField = ScaleTransition(
|
||||
scale: scaleAnimation,
|
||||
child: AnimatedBuilder(
|
||||
animation: sizeAnimation,
|
||||
builder: (context, child) => ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: sizeAnimation.value),
|
||||
child: child,
|
||||
),
|
||||
child: textField,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.inertiaController != null) {
|
||||
textField = AnimatedBuilder(
|
||||
animation: fieldTranslateAnimation,
|
||||
builder: (context, child) => Transform.translate(
|
||||
offset: Offset(fieldTranslateAnimation.value, 0),
|
||||
child: child,
|
||||
),
|
||||
child: textField,
|
||||
);
|
||||
}
|
||||
|
||||
return textField;
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedPasswordTextFormField extends StatefulWidget {
|
||||
AnimatedPasswordTextFormField({
|
||||
Key key,
|
||||
this.interval = const Interval(0.0, 1.0),
|
||||
@required this.animatedWidth,
|
||||
this.loadingController,
|
||||
this.inertiaController,
|
||||
this.inertiaDirection,
|
||||
this.enabled = true,
|
||||
this.labelText,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.validator,
|
||||
this.onFieldSubmitted,
|
||||
this.onSaved,
|
||||
}) : assert((inertiaController == null && inertiaDirection == null) ||
|
||||
(inertiaController != null && inertiaDirection != null)),
|
||||
super(key: key);
|
||||
|
||||
final Interval interval;
|
||||
final AnimationController loadingController;
|
||||
final AnimationController inertiaController;
|
||||
final double animatedWidth;
|
||||
final bool enabled;
|
||||
final String labelText;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction textInputAction;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final FormFieldValidator<String> validator;
|
||||
final ValueChanged<String> onFieldSubmitted;
|
||||
final FormFieldSetter<String> onSaved;
|
||||
final TextFieldInertiaDirection inertiaDirection;
|
||||
|
||||
@override
|
||||
_AnimatedPasswordTextFormFieldState createState() =>
|
||||
_AnimatedPasswordTextFormFieldState();
|
||||
}
|
||||
|
||||
class _AnimatedPasswordTextFormFieldState
|
||||
extends State<AnimatedPasswordTextFormField> {
|
||||
var _obscureText = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedTextFormField(
|
||||
interval: widget.interval,
|
||||
loadingController: widget.loadingController,
|
||||
inertiaController: widget.inertiaController,
|
||||
width: widget.animatedWidth,
|
||||
enabled: widget.enabled,
|
||||
labelText: widget.labelText,
|
||||
prefixIcon: Icon(Icons.lock, size: 20),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() => _obscureText = !_obscureText),
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
child: AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
firstCurve: Curves.easeInOutSine,
|
||||
secondCurve: Curves.easeInOutSine,
|
||||
alignment: Alignment.center,
|
||||
layoutBuilder: (Widget topChild, _, Widget bottomChild, __) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[bottomChild, topChild],
|
||||
);
|
||||
},
|
||||
firstChild: Icon(
|
||||
Icons.visibility,
|
||||
size: 25.0,
|
||||
semanticLabel: 'show password',
|
||||
),
|
||||
secondChild: Icon(
|
||||
Icons.visibility_off,
|
||||
size: 25.0,
|
||||
semanticLabel: 'hide password',
|
||||
),
|
||||
crossFadeState: _obscureText
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
),
|
||||
),
|
||||
obscureText: _obscureText,
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.textInputAction,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
validator: widget.validator,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
onSaved: widget.onSaved,
|
||||
inertiaDirection: widget.inertiaDirection,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,465 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class AnimatedButton extends StatefulWidget {
|
||||
AnimatedButton({
|
||||
Key key,
|
||||
@required this.text,
|
||||
@required this.onPressed,
|
||||
@required this.controller,
|
||||
this.textColor,
|
||||
this.loadingColor,
|
||||
this.color,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final Color color;
|
||||
final Color textColor;
|
||||
final Color loadingColor;
|
||||
final Function onPressed;
|
||||
final AnimationController controller;
|
||||
|
||||
@override
|
||||
_AnimatedButtonState createState() => _AnimatedButtonState();
|
||||
}
|
||||
|
||||
class _AnimatedButtonState extends State<AnimatedButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Animation<double> _sizeAnimation;
|
||||
Animation<double> _textOpacityAnimation;
|
||||
Animation<double> _buttonOpacityAnimation;
|
||||
Animation<double> _ringThicknessAnimation;
|
||||
Animation<double> _ringOpacityAnimation;
|
||||
Animation<Color> _colorAnimation;
|
||||
var _isLoading = false;
|
||||
var _hover = false;
|
||||
var _width = 120.0;
|
||||
|
||||
Color _color;
|
||||
Color _loadingColor;
|
||||
|
||||
static const _height = 40.0;
|
||||
static const _loadingCircleRadius = _height / 2;
|
||||
static const _loadingCircleThickness = 4.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_textOpacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: Interval(0.0, .25),
|
||||
),
|
||||
);
|
||||
|
||||
// _colorAnimation
|
||||
// _width, _sizeAnimation
|
||||
|
||||
_buttonOpacityAnimation =
|
||||
Tween<double>(begin: 1.0, end: 0.0).animate(CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: Threshold(.65),
|
||||
));
|
||||
|
||||
_ringThicknessAnimation =
|
||||
Tween<double>(begin: _loadingCircleRadius, end: _loadingCircleThickness)
|
||||
.animate(CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: Interval(.65, .85),
|
||||
));
|
||||
_ringOpacityAnimation =
|
||||
Tween<double>(begin: 1.0, end: 0.0).animate(CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: Interval(.85, 1.0),
|
||||
));
|
||||
|
||||
widget.controller.addStatusListener(handleStatusChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_updateColorAnimation();
|
||||
_updateWidth();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
void _updateColorAnimation() {
|
||||
final theme = Theme.of(context);
|
||||
final buttonTheme = theme.floatingActionButtonTheme;
|
||||
|
||||
_color = widget.color ?? buttonTheme.backgroundColor;
|
||||
_loadingColor = widget.loadingColor ?? theme.accentColor;
|
||||
|
||||
_colorAnimation = ColorTween(
|
||||
begin: _color,
|
||||
end: _loadingColor,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: const Interval(0.0, .65, curve: Curves.fastOutSlowIn),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.color != widget.color ||
|
||||
oldWidget.loadingColor != widget.loadingColor) {
|
||||
_updateColorAnimation();
|
||||
}
|
||||
|
||||
if (oldWidget.text != widget.text) {
|
||||
_updateWidth();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.controller.removeStatusListener(handleStatusChanged);
|
||||
}
|
||||
|
||||
void handleStatusChanged(status) {
|
||||
if (status == AnimationStatus.forward) {
|
||||
setState(() => _isLoading = true);
|
||||
}
|
||||
if (status == AnimationStatus.dismissed) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// sets width and size animation
|
||||
void _updateWidth() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = theme.textTheme.button.fontSize;
|
||||
final renderParagraph = RenderParagraph(
|
||||
TextSpan(
|
||||
text: widget.text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: theme.textTheme.button.fontWeight,
|
||||
letterSpacing: theme.textTheme.button.letterSpacing,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: 1,
|
||||
);
|
||||
|
||||
renderParagraph.layout(BoxConstraints(minWidth: 120.0));
|
||||
|
||||
// text width based on fontSize, plus 45.0 for padding
|
||||
var textWidth =
|
||||
renderParagraph.getMinIntrinsicWidth(fontSize).ceilToDouble() + 45.0;
|
||||
|
||||
// button width is min 120.0 and max 240.0
|
||||
_width = textWidth > 120.0 && textWidth < 240.0
|
||||
? textWidth
|
||||
: textWidth >= 240.0 ? 240.0 : 120.0;
|
||||
|
||||
_sizeAnimation = Tween<double>(begin: 1.0, end: _height / _width)
|
||||
.animate(CurvedAnimation(
|
||||
parent: widget.controller,
|
||||
curve: Interval(0.0, .65, curve: Curves.fastOutSlowIn),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildButtonText(ThemeData theme) {
|
||||
return FadeTransition(
|
||||
opacity: _textOpacityAnimation,
|
||||
child: AnimatedText(
|
||||
text: widget.text,
|
||||
style: TextStyle(color: widget.textColor ?? Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildButton(ThemeData theme) {
|
||||
final buttonTheme = theme.floatingActionButtonTheme;
|
||||
|
||||
return FadeTransition(
|
||||
opacity: _buttonOpacityAnimation,
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: AnimatedBuilder(
|
||||
animation: _colorAnimation,
|
||||
builder: (context, child) => Material(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_height/2)
|
||||
),
|
||||
color: _colorAnimation.value,
|
||||
child: child,
|
||||
shadowColor: _color,
|
||||
elevation: !_isLoading
|
||||
? (_hover ? buttonTheme.highlightElevation : buttonTheme.elevation)
|
||||
: 0,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: !_isLoading ? widget.onPressed : null,
|
||||
splashColor: buttonTheme.splashColor,
|
||||
customBorder: buttonTheme.shape,
|
||||
onHighlightChanged: (value) => setState(() => _hover = value),
|
||||
child: SizeTransition(
|
||||
sizeFactor: _sizeAnimation,
|
||||
axis: Axis.horizontal,
|
||||
child: Container(
|
||||
width: _width,
|
||||
height: _height,
|
||||
alignment: Alignment.center,
|
||||
child: _buildButtonText(theme),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
FadeTransition(
|
||||
opacity: _ringOpacityAnimation,
|
||||
child: AnimatedBuilder(
|
||||
animation: _ringThicknessAnimation,
|
||||
builder: (context, child) => Ring(
|
||||
color: widget.loadingColor,
|
||||
size: _height,
|
||||
thickness: _ringThicknessAnimation.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isLoading)
|
||||
SizedBox(
|
||||
width: _height - _loadingCircleThickness,
|
||||
height: _height - _loadingCircleThickness,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(widget.loadingColor),
|
||||
// backgroundColor: Colors.red,
|
||||
strokeWidth: _loadingCircleThickness,
|
||||
),
|
||||
),
|
||||
_buildButton(theme),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Ring extends StatelessWidget {
|
||||
Ring({
|
||||
Key key,
|
||||
this.color,
|
||||
this.size = 40.0,
|
||||
this.thickness = 2.0,
|
||||
this.value = 1.0,
|
||||
}) : assert(size - thickness > 0),
|
||||
assert(thickness >= 0),
|
||||
super(key: key);
|
||||
|
||||
final Color color;
|
||||
final double size;
|
||||
final double thickness;
|
||||
final double value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: size - thickness,
|
||||
height: size - thickness,
|
||||
child: thickness == 0
|
||||
? null
|
||||
: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
strokeWidth: thickness,
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum AnimatedTextRotation { up, down }
|
||||
|
||||
/// https://medium.com/flutter-community/flutter-challenge-3d-bottom-navigation-bar-48952a5fd996
|
||||
class AnimatedText extends StatefulWidget {
|
||||
AnimatedText({
|
||||
Key key,
|
||||
@required this.text,
|
||||
this.style,
|
||||
this.textRotation = AnimatedTextRotation.up,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final TextStyle style;
|
||||
final AnimatedTextRotation textRotation;
|
||||
|
||||
@override
|
||||
_AnimatedTextState createState() => _AnimatedTextState();
|
||||
}
|
||||
|
||||
class _AnimatedTextState extends State<AnimatedText>
|
||||
with SingleTickerProviderStateMixin {
|
||||
var _newText = '';
|
||||
var _oldText = '';
|
||||
var _layoutHeight = 0.0;
|
||||
final _textKey = GlobalKey();
|
||||
|
||||
Animation<double> _animation;
|
||||
AnimationController _controller;
|
||||
|
||||
double get radius => _layoutHeight / 2;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
|
||||
_animation = Tween<double>(begin: 0.0, end: pi / 2).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutBack,
|
||||
));
|
||||
|
||||
_oldText = widget.text;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() => _layoutHeight = getWidgetSize(_textKey)?.height);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedText oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (widget.text != oldWidget.text) {
|
||||
_oldText = oldWidget.text;
|
||||
_newText = widget.text;
|
||||
_controller.forward().then((_) {
|
||||
setState(() {
|
||||
final t = _oldText;
|
||||
_oldText = _newText;
|
||||
_newText = t;
|
||||
});
|
||||
_controller.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
}
|
||||
|
||||
Matrix4 get _matrix {
|
||||
// Fix: The text is not centered after applying perspective effect in the web build. Idk why
|
||||
if (kIsWeb) {
|
||||
return Matrix4.identity();
|
||||
}
|
||||
return Matrix4.identity()..setEntry(3, 2, .006);
|
||||
}
|
||||
|
||||
Matrix4 _getFrontSideUp(double value) {
|
||||
return _matrix
|
||||
..translate(
|
||||
0.0,
|
||||
-radius * sin(_animation.value),
|
||||
-radius * cos(_animation.value),
|
||||
)
|
||||
..rotateX(-_animation.value); // 0 -> -pi/2
|
||||
}
|
||||
|
||||
Matrix4 _getBackSideUp(double value) {
|
||||
return _matrix
|
||||
..translate(
|
||||
0.0,
|
||||
radius * cos(_animation.value),
|
||||
-radius * sin(_animation.value),
|
||||
)
|
||||
..rotateX((pi / 2) - _animation.value); // pi/2 -> 0
|
||||
}
|
||||
|
||||
Matrix4 _getFrontSideDown(double value) {
|
||||
return _matrix
|
||||
..translate(
|
||||
0.0,
|
||||
radius * sin(_animation.value),
|
||||
-radius * cos(_animation.value),
|
||||
)
|
||||
..rotateX(_animation.value); // 0 -> pi/2
|
||||
}
|
||||
|
||||
Matrix4 _getBackSideDown(double value) {
|
||||
return _matrix
|
||||
..translate(
|
||||
0.0,
|
||||
-radius * cos(_animation.value),
|
||||
-radius * sin(_animation.value),
|
||||
)
|
||||
..rotateX(_animation.value - pi / 2); // -pi/2 -> 0
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final rollUp = widget.textRotation == AnimatedTextRotation.up;
|
||||
final oldText = Text(
|
||||
_oldText,
|
||||
key: _textKey,
|
||||
style: widget.style,
|
||||
overflow: TextOverflow.visible,
|
||||
softWrap: false,
|
||||
);
|
||||
final newText = Text(
|
||||
_newText,
|
||||
style: widget.style,
|
||||
overflow: TextOverflow.visible,
|
||||
softWrap: false,
|
||||
);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) => Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
if (_animation.value <= toRadian(85))
|
||||
Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: rollUp
|
||||
? _getFrontSideUp(_animation.value)
|
||||
: _getFrontSideDown(_animation.value),
|
||||
child: oldText,
|
||||
),
|
||||
if (_animation.value >= toRadian(5))
|
||||
Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: rollUp
|
||||
? _getBackSideUp(_animation.value)
|
||||
: _getBackSideDown(_animation.value),
|
||||
child: newText,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Helpers
|
||||
double toRadian(double degree) => degree * pi / 180;
|
||||
double lerp(double start, double end, double percent) => (start + percent * (end - start));
|
||||
Size getWidgetSize(GlobalKey key) {
|
||||
final RenderBox renderBox = key.currentContext?.findRenderObject();
|
||||
return renderBox?.size;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
enum TextFieldInertiaDirection {
|
||||
left,
|
||||
right,
|
||||
}
|
||||
|
||||
Interval _getInternalInterval(
|
||||
double start,
|
||||
double end,
|
||||
double externalStart,
|
||||
double externalEnd, [
|
||||
Curve curve = Curves.linear,
|
||||
]) {
|
||||
return Interval(
|
||||
start + (end - start) * externalStart,
|
||||
start + (end - start) * externalEnd,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
class AnimatedTextFormField extends StatefulWidget {
|
||||
AnimatedTextFormField({
|
||||
Key key,
|
||||
this.interval = const Interval(0.0, 1.0),
|
||||
@required this.width,
|
||||
this.loadingController,
|
||||
this.inertiaController,
|
||||
this.inertiaDirection,
|
||||
this.enabled = true,
|
||||
this.labelText,
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.obscureText = false,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.validator,
|
||||
this.onFieldSubmitted,
|
||||
this.onSaved,
|
||||
}) : assert((inertiaController == null && inertiaDirection == null) ||
|
||||
(inertiaController != null && inertiaDirection != null)),
|
||||
super(key: key);
|
||||
|
||||
final Interval interval;
|
||||
final AnimationController loadingController;
|
||||
final AnimationController inertiaController;
|
||||
final double width;
|
||||
final bool enabled;
|
||||
final String labelText;
|
||||
final Widget prefixIcon;
|
||||
final Widget suffixIcon;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction textInputAction;
|
||||
final bool obscureText;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final FormFieldValidator<String> validator;
|
||||
final ValueChanged<String> onFieldSubmitted;
|
||||
final FormFieldSetter<String> onSaved;
|
||||
final TextFieldInertiaDirection inertiaDirection;
|
||||
|
||||
@override
|
||||
_AnimatedTextFormFieldState createState() => _AnimatedTextFormFieldState();
|
||||
}
|
||||
|
||||
class _AnimatedTextFormFieldState extends State<AnimatedTextFormField> {
|
||||
Animation<double> scaleAnimation;
|
||||
Animation<double> sizeAnimation;
|
||||
Animation<double> suffixIconOpacityAnimation;
|
||||
|
||||
Animation<double> fieldTranslateAnimation;
|
||||
Animation<double> iconRotationAnimation;
|
||||
Animation<double> iconTranslateAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.inertiaController?.addStatusListener(handleAnimationStatus);
|
||||
|
||||
final interval = widget.interval;
|
||||
final loadingController = widget.loadingController;
|
||||
|
||||
if (loadingController != null) {
|
||||
scaleAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(
|
||||
0, .2, interval.begin, interval.end, Curves.easeOutBack),
|
||||
));
|
||||
suffixIconOpacityAnimation =
|
||||
Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(.65, 1.0, interval.begin, interval.end),
|
||||
));
|
||||
_updateSizeAnimation();
|
||||
}
|
||||
|
||||
final inertiaController = widget.inertiaController;
|
||||
final inertiaDirection = widget.inertiaDirection;
|
||||
final sign = inertiaDirection == TextFieldInertiaDirection.right ? 1 : -1;
|
||||
|
||||
if (inertiaController != null) {
|
||||
fieldTranslateAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: sign * 15.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(0, .5, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
iconRotationAnimation =
|
||||
Tween<double>(begin: 0.0, end: sign * pi / 12 /* ~15deg */)
|
||||
.animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(.5, 1.0, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
iconTranslateAnimation =
|
||||
Tween<double>(begin: 0.0, end: 8.0).animate(CurvedAnimation(
|
||||
parent: inertiaController,
|
||||
curve: Interval(.5, 1.0, curve: Curves.easeOut),
|
||||
reverseCurve: Curves.easeIn,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSizeAnimation() {
|
||||
final interval = widget.interval;
|
||||
final loadingController = widget.loadingController;
|
||||
|
||||
sizeAnimation = Tween<double>(
|
||||
begin: 48.0,
|
||||
end: widget.width,
|
||||
).animate(CurvedAnimation(
|
||||
parent: loadingController,
|
||||
curve: _getInternalInterval(
|
||||
.2, 1.0, interval.begin, interval.end, Curves.linearToEaseOut),
|
||||
reverseCurve: Curves.easeInExpo,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedTextFormField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.width != widget.width) {
|
||||
_updateSizeAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
widget.inertiaController?.removeStatusListener(handleAnimationStatus);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void handleAnimationStatus(status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
widget.inertiaController?.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildInertiaAnimation(Widget child) {
|
||||
if (widget.inertiaController == null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: iconTranslateAnimation,
|
||||
builder: (context, child) => Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..translate(iconTranslateAnimation.value)
|
||||
..rotateZ(iconRotationAnimation.value),
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _getInputDecoration(ThemeData theme) {
|
||||
return InputDecoration(
|
||||
labelText: widget.labelText,
|
||||
prefixIcon: _buildInertiaAnimation(widget.prefixIcon),
|
||||
suffixIcon: _buildInertiaAnimation(widget.loadingController != null
|
||||
? FadeTransition(
|
||||
opacity: suffixIconOpacityAnimation,
|
||||
child: widget.suffixIcon,
|
||||
)
|
||||
: widget.suffixIcon),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
Widget textField = TextFormField(
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
decoration: _getInputDecoration(theme),
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.textInputAction,
|
||||
obscureText: widget.obscureText,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
onSaved: widget.onSaved,
|
||||
validator: widget.validator,
|
||||
enabled: widget.enabled,
|
||||
);
|
||||
|
||||
if (widget.loadingController != null) {
|
||||
textField = ScaleTransition(
|
||||
scale: scaleAnimation,
|
||||
child: AnimatedBuilder(
|
||||
animation: sizeAnimation,
|
||||
builder: (context, child) => ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: sizeAnimation.value),
|
||||
child: child,
|
||||
),
|
||||
child: textField,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.inertiaController != null) {
|
||||
textField = AnimatedBuilder(
|
||||
animation: fieldTranslateAnimation,
|
||||
builder: (context, child) => Transform.translate(
|
||||
offset: Offset(fieldTranslateAnimation.value, 0),
|
||||
child: child,
|
||||
),
|
||||
child: textField,
|
||||
);
|
||||
}
|
||||
|
||||
return textField;
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedPasswordTextFormField extends StatefulWidget {
|
||||
AnimatedPasswordTextFormField({
|
||||
Key key,
|
||||
this.interval = const Interval(0.0, 1.0),
|
||||
@required this.animatedWidth,
|
||||
this.loadingController,
|
||||
this.inertiaController,
|
||||
this.inertiaDirection,
|
||||
this.enabled = true,
|
||||
this.labelText,
|
||||
this.keyboardType,
|
||||
this.textInputAction,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.validator,
|
||||
this.onFieldSubmitted,
|
||||
this.onSaved,
|
||||
}) : assert((inertiaController == null && inertiaDirection == null) ||
|
||||
(inertiaController != null && inertiaDirection != null)),
|
||||
super(key: key);
|
||||
|
||||
final Interval interval;
|
||||
final AnimationController loadingController;
|
||||
final AnimationController inertiaController;
|
||||
final double animatedWidth;
|
||||
final bool enabled;
|
||||
final String labelText;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction textInputAction;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final FormFieldValidator<String> validator;
|
||||
final ValueChanged<String> onFieldSubmitted;
|
||||
final FormFieldSetter<String> onSaved;
|
||||
final TextFieldInertiaDirection inertiaDirection;
|
||||
|
||||
@override
|
||||
_AnimatedPasswordTextFormFieldState createState() =>
|
||||
_AnimatedPasswordTextFormFieldState();
|
||||
}
|
||||
|
||||
class _AnimatedPasswordTextFormFieldState
|
||||
extends State<AnimatedPasswordTextFormField> {
|
||||
var _obscureText = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedTextFormField(
|
||||
interval: widget.interval,
|
||||
loadingController: widget.loadingController,
|
||||
inertiaController: widget.inertiaController,
|
||||
width: widget.animatedWidth,
|
||||
enabled: widget.enabled,
|
||||
labelText: widget.labelText,
|
||||
prefixIcon: Icon(FontAwesomeIcons.lock, size: 20),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() => _obscureText = !_obscureText),
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
child: AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
firstCurve: Curves.easeInOutSine,
|
||||
secondCurve: Curves.easeInOutSine,
|
||||
alignment: Alignment.center,
|
||||
layoutBuilder: (Widget topChild, _, Widget bottomChild, __) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[bottomChild, topChild],
|
||||
);
|
||||
},
|
||||
firstChild: Icon(
|
||||
Icons.visibility,
|
||||
size: 25.0,
|
||||
semanticLabel: 'show password',
|
||||
),
|
||||
secondChild: Icon(
|
||||
Icons.visibility_off,
|
||||
size: 25.0,
|
||||
semanticLabel: 'hide password',
|
||||
),
|
||||
crossFadeState: _obscureText
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
),
|
||||
),
|
||||
obscureText: _obscureText,
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.textInputAction,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
validator: widget.validator,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
onSaved: widget.onSaved,
|
||||
inertiaDirection: widget.inertiaDirection,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue