CW-102 fix logic for ionia issues (#403)

This commit is contained in:
Godwin Asuquo 2022-07-06 20:13:19 +03:00 committed by GitHub
parent 828d14be98
commit 73cbdc4546
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1952 additions and 514 deletions

BIN
assets/images/airplane.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

BIN
assets/images/category.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

BIN
assets/images/copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

BIN
assets/images/delivery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

BIN
assets/images/food.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

BIN
assets/images/gaming.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

BIN
assets/images/global.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

BIN
assets/images/tshirt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

View file

@ -1,3 +1,6 @@
import 'package:cake_wallet/anypay/any_pay_chain.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/monero_amount_format.dart';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/anypay/any_pay_payment_instruction.dart';
@ -35,4 +38,27 @@ class AnyPayPayment {
final String chain;
final String network;
final List<AnyPayPaymentInstruction> instructions;
String get totalAmount {
final total = instructions
.fold<int>(0, (int acc, instruction) => acc + instruction.outputs
.fold<int>(0, (int outAcc, out) => outAcc + out.amount));
switch (chain) {
case AnyPayChain.xmr:
return moneroAmountToString(amount: total);
case AnyPayChain.btc:
return bitcoinAmountToString(amount: total);
case AnyPayChain.ltc:
return bitcoinAmountToString(amount: total);
default:
return null;
}
}
List<String> get outAddresses {
return instructions
.map((instuction) => instuction.outputs.map((out) => out.address))
.expand((e) => e)
.toList();
}
}

View file

@ -1,6 +1,10 @@
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wake_lock.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:cake_wallet/ionia/ionia_api.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
@ -8,9 +12,14 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart';
@ -107,6 +116,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
@ -263,7 +273,6 @@ Future setup(
fiatConvertationStore: getIt.get<FiatConversionStore>()));
getIt.registerFactory(() => DashboardViewModel(
balanceViewModel: getIt.get<BalanceViewModel>(),
appStore: getIt.get<AppStore>(),
tradesStore: getIt.get<TradesStore>(),
@ -564,10 +573,6 @@ Future setup(
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(() => EditBackupPasswordViewModel(
getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
..init());
getIt.registerFactory(
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
@ -600,10 +605,7 @@ Future setup(
final url = args.first as String;
final buyViewModel = args[1] as BuyViewModel;
return BuyWebViewPage(
buyViewModel: buyViewModel,
ordersStore: getIt.get<OrdersStore>(),
url: url);
return BuyWebViewPage(buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
});
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
@ -656,41 +658,65 @@ Future setup(
getIt.registerFactory<IoniaService>(
() => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
getIt.registerFactory<IoniaAnyPay>(
() => IoniaAnyPay(
getIt.get<IoniaService>(),
getIt.get<AnyPayApi>(),
getIt.get<AppStore>().wallet));
getIt.registerFactory(
() => IoniaViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory<IoniaFilterViewModel>(() => IoniaFilterViewModel());
getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaMerchPurchaseViewModel(getIt.get<IoniaAnyPay>()));
getIt.registerFactory(() => IoniaAccountViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaAuthViewModel>()));
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
final email = args.first as String;
final ioniaViewModel = args[1] as IoniaViewModel;
final ioniaAuthViewModel = args[1] as IoniaAuthViewModel;
return IoniaVerifyIoniaOtp(ioniaViewModel, email);
return IoniaVerifyIoniaOtp(ioniaAuthViewModel, email);
});
getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaGiftCardsListViewModel>()));
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
final merchant = args.first as IoniaMerchant;
return IoniaBuyGiftCardPage(merchant);
});
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
final merchant = args.first as IoniaMerchant;
return IoniaBuyGiftCardDetailPage(merchant);
return IoniaBuyGiftCardPage(getIt.get<IoniaMerchPurchaseViewModel>(), merchant);
});
getIt.registerFactory(() => IoniaManageCardsPage(getIt.get<IoniaViewModel>()));
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
final amount = args.first as String;
final merchant = args.last as IoniaMerchant;
getIt.registerFactory(() => IoniaDebitCardPage(getIt.get<IoniaViewModel>()));
return IoniaBuyGiftCardDetailPage(amount, getIt.get<IoniaMerchPurchaseViewModel>(), merchant);
});
getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get<IoniaViewModel>()));
getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
final amount = args.first as String;
final merchant = args.last as IoniaMerchant;
return IoniaCustomTipPage(getIt.get<IoniaMerchPurchaseViewModel>(), amount, merchant);
});
getIt.registerFactory(() => IoniaManageCardsPage(getIt.get<IoniaGiftCardsListViewModel>()));
getIt.registerFactory(() => IoniaDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get<IoniaGiftCardsListViewModel>()));
getIt.registerFactory(() => IoniaAccountPage(getIt.get<IoniaAccountViewModel>()));
getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
_isSetupFinished = true;
}

View file

@ -23,12 +23,11 @@ class IoniaAnyPay {
Future<AnyPayPayment> purchase({
@required String merchId,
@required double amount,
@required String currency}) async {
@required double amount}) async {
final invoice = await ioniaService.purchaseGiftCard(
merchId: merchId,
amount: amount,
currency: currency);
currency: wallet.currency.title.toUpperCase());
return anyPayApi.paymentRequest(invoice.uri);
}

View file

@ -1,20 +1,18 @@
class IoniaCategory {
const IoniaCategory(this.title, this.ids);
const IoniaCategory({this.index, this.title, this.ids, this.iconPath});
static const allCategories = <IoniaCategory>[
apparel,
onlineOnly,
food,
entertainment,
delivery,
travel];
static const apparel = IoniaCategory('Apparel', [1]);
static const onlineOnly = IoniaCategory('Online Only', [13, 43]);
static const food = IoniaCategory('Food', [4]);
static const entertainment = IoniaCategory('Entertainment', [5]);
static const delivery = IoniaCategory('Delivery', [114, 109]);
static const travel = IoniaCategory('Travel', [12]);
static const allCategories = <IoniaCategory>[all, apparel, onlineOnly, food, entertainment, delivery, travel];
static const all = IoniaCategory(index: 0, title: 'All', ids: [], iconPath: 'assets/images/category.png');
static const apparel = IoniaCategory(index: 1, title: 'Apparel', ids: [1], iconPath: 'assets/images/tshirt.png');
static const onlineOnly = IoniaCategory(index: 2, title: 'Online Only', ids: [13, 43], iconPath: 'assets/images/global.png');
static const food = IoniaCategory(index: 3, title: 'Food', ids: [4], iconPath: 'assets/images/food.png');
static const entertainment = IoniaCategory(index: 4, title: 'Entertainment', ids: [5], iconPath: 'assets/images/gaming.png');
static const delivery = IoniaCategory(index: 5, title: 'Delivery', ids: [114, 109], iconPath: 'assets/images/delivery.png');
static const travel = IoniaCategory(index: 6, title: 'Travel', ids: [12], iconPath: 'assets/images/airplane.png');
final String title;
final List<int> ids;
final int index;
final String title;
final List<int> ids;
final String iconPath;
}

View file

@ -1,6 +1,5 @@
import 'package:flutter/foundation.dart';
class IoniaMerchant {
class IoniaMerchant {
IoniaMerchant({
@required this.id,
@required this.legalName,
@ -114,57 +113,49 @@ class IoniaMerchant {
isPayLater: element["IsPayLater"] as bool);
}
final int id;
final String legalName;
final String systemName;
final String description;
final String website;
final String termsAndConditions;
final String logoUrl;
final String cardImageUrl;
final String cardholderAgreement;
final double purchaseFee;
final double revenueShare;
final double marketingFee;
final double minimumDiscount;
final double level1;
final double level2;
final double level3;
final double level4;
final double level5;
final double level6;
final double level7;
final bool isActive;
final bool isDeleted;
final bool isOnline;
final bool isPhysical;
final bool isVariablePurchase;
final double minimumCardPurchase;
final double maximumCardPurchase;
final bool acceptsTips;
final String createdDateFormatted;
final int createdBy;
final bool isRegional;
final String modifiedDateFormatted;
final int modifiedBy;
final String usageInstructions;
final String usageInstructionsBak;
final int paymentGatewayId;
final int giftCardGatewayId;
final bool isHtmlDescription;
final String purchaseInstructions;
final String balanceInstructions;
final double amountPerCard;
final String processingMessage;
final bool hasBarcode;
final bool hasInventory;
final bool isVoidable;
final String receiptMessage;
final String cssBorderCode;
final String paymentInstructions;
final String alderSku;
final String ngcSku;
final String acceptedCurrency;
final String deepLink;
final bool isPayLater;
final int id; final String legalName; final String systemName; final String description; final String website; final String termsAndConditions; final String logoUrl; final String cardImageUrl; final String cardholderAgreement; final double purchaseFee;
final double revenueShare;
final double marketingFee;
final double minimumDiscount;
final double level1;
final double level2;
final double level3;
final double level4;
final double level5;
final double level6;
final double level7;
final bool isActive;
final bool isDeleted;
final bool isOnline;
final bool isPhysical;
final bool isVariablePurchase;
final double minimumCardPurchase;
final double maximumCardPurchase;
final bool acceptsTips;
final String createdDateFormatted;
final int createdBy;
final bool isRegional;
final String modifiedDateFormatted;
final int modifiedBy;
final String usageInstructions;
final String usageInstructionsBak;
final int paymentGatewayId;
final int giftCardGatewayId;
final bool isHtmlDescription;
final String purchaseInstructions;
final String balanceInstructions;
final double amountPerCard;
final String processingMessage;
final bool hasBarcode;
final bool hasInventory;
final bool isVoidable;
final String receiptMessage;
final String cssBorderCode;
final String paymentInstructions;
final String alderSku;
final String ngcSku;
final String acceptedCurrency;
final String deepLink;
final bool isPayLater;
}

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/ionia/ionia_category.dart';
class IoniaService {
IoniaService(this.secureStorage, this.ioniaApi);
static const ioniaEmailStorageKey = 'ionia_email';
static const ioniaUsernameStorageKey = 'ionia_username';
static const ioniaPasswordStorageKey = 'ionia_password';
@ -22,6 +23,7 @@ class IoniaService {
Future<void> createUser(String email) async {
final username = await ioniaApi.createUser(email, clientId: clientId);
await secureStorage.write(key: ioniaEmailStorageKey, value: email);
await secureStorage.write(key: ioniaUsernameStorageKey, value: username);
}
@ -33,6 +35,10 @@ class IoniaService {
await secureStorage.write(key: ioniaPasswordStorageKey, value: credentials.password);
}
Future<String> getUserEmail() async {
return secureStorage.read(key: ioniaEmailStorageKey);
}
// Check is user logined
Future<bool> isLogined() async {

12
lib/ionia/ionia_tip.dart Normal file
View file

@ -0,0 +1,12 @@
class IoniaTip {
const IoniaTip({this.originalAmount, this.percentage});
final double originalAmount;
final double percentage;
double get additionalAmount => originalAmount * percentage / 100;
static const tipList = [
IoniaTip(originalAmount: 0, percentage: 0),
IoniaTip(originalAmount: 10, percentage: 10),
IoniaTip(originalAmount: 20, percentage: 20)
];
}

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -174,7 +176,8 @@ Future<void> initialSetup(
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions,
ordersSource: ordersSource,
unspentCoinsInfoSource: unspentCoinsInfoSource);
unspentCoinsInfoSource: unspentCoinsInfoSource,
);
await bootstrap(navigatorKey);
monero?.onStartup();
}

View file

@ -5,6 +5,9 @@ import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
@ -433,6 +436,16 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaActivateDebitCardPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaActivateDebitCardPage>());
case Routes.ioniaAccountPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
case Routes.ioniaAccountCardsPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
case Routes.ioniaCustomTipPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -69,5 +69,8 @@ class Routes {
static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
static const ioniaDebitCardPage = '/debit_card_page';
static const ioniaActivateDebitCardPage = '/activate_debit_card_page';
static const ioniaAccountPage = 'ionia_account_page';
static const ioniaAccountCardsPage = 'ionia_account_cards_page';
static const ioniaCustomTipPage = 'ionia_custom_tip_page';
}

View file

@ -8,22 +8,22 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaCreateAccountPage extends BasePage {
IoniaCreateAccountPage(this._ioniaViewModel)
IoniaCreateAccountPage(this._authViewModel)
: _emailFocus = FocusNode(),
_emailController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_emailController.text = _ioniaViewModel.email;
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
_emailController.text = _authViewModel.email;
_emailController.addListener(() => _authViewModel.email = _emailController.text);
}
final IoniaViewModel _ioniaViewModel;
final IoniaAuthViewModel _authViewModel;
final GlobalKey<FormState> _formKey;
@ -42,12 +42,12 @@ class IoniaCreateAccountPage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) {
reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) {
if (state is IoniaCreateStateFailure) {
_onCreateUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onCreateSuccessful(context, _ioniaViewModel);
_onCreateSuccessful(context, _authViewModel);
}
});
@ -59,6 +59,7 @@ class IoniaCreateAccountPage extends BasePage {
hintText: S.of(context).email_address,
focusNode: _emailFocus,
validator: EmailValidator(),
keyboardType: TextInputType.emailAddress,
controller: _emailController,
),
),
@ -75,9 +76,9 @@ class IoniaCreateAccountPage extends BasePage {
if (!_formKey.currentState.validate()) {
return;
}
await _ioniaViewModel.createUser(_emailController.text);
await _authViewModel.createUser(_emailController.text);
},
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
isLoading: _authViewModel.createUserState is IoniaCreateStateLoading,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
@ -133,9 +134,9 @@ class IoniaCreateAccountPage extends BasePage {
});
}
void _onCreateSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed(
void _onCreateSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
context,
Routes.ioniaVerifyIoniaOtpPage,
arguments: [ioniaViewModel.email, ioniaViewModel],
arguments: [authViewModel.email, authViewModel],
);
}

View file

@ -8,23 +8,23 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaLoginPage extends BasePage {
IoniaLoginPage(this._ioniaViewModel)
IoniaLoginPage(this._authViewModel)
: _formKey = GlobalKey<FormState>(),
_emailController = TextEditingController() {
_emailController.text = _ioniaViewModel.email;
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
_emailController.text = _authViewModel.email;
_emailController.addListener(() => _authViewModel.email = _emailController.text);
}
final GlobalKey<FormState> _formKey;
final IoniaViewModel _ioniaViewModel;
final IoniaAuthViewModel _authViewModel;
@override
Color get titleColor => Colors.black;
@ -43,12 +43,12 @@ class IoniaLoginPage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) {
reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) {
if (state is IoniaCreateStateFailure) {
_onLoginUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onLoginSuccessful(context, _ioniaViewModel);
_onLoginSuccessful(context, _authViewModel);
}
});
return ScrollableWithBottomSection(
@ -57,6 +57,7 @@ class IoniaLoginPage extends BasePage {
key: _formKey,
child: BaseTextFormField(
hintText: S.of(context).email_address,
keyboardType: TextInputType.emailAddress,
validator: EmailValidator(),
controller: _emailController,
),
@ -74,9 +75,9 @@ class IoniaLoginPage extends BasePage {
if (!_formKey.currentState.validate()) {
return;
}
await _ioniaViewModel.createUser(_emailController.text);
await _authViewModel.createUser(_emailController.text);
},
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
isLoading: _authViewModel.createUserState is IoniaCreateStateLoading,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
@ -103,9 +104,9 @@ class IoniaLoginPage extends BasePage {
});
}
void _onLoginSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed(
void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
context,
Routes.ioniaVerifyIoniaOtpPage,
arguments: [ioniaViewModel.email, ioniaViewModel],
arguments: [authViewModel.email, authViewModel],
);
}

View file

@ -4,33 +4,34 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
class IoniaVerifyIoniaOtp extends BasePage {
IoniaVerifyIoniaOtp(this._ioniaViewModel, this._email)
IoniaVerifyIoniaOtp(this._authViewModel, this._email)
: _codeController = TextEditingController(),
_codeFocus = FocusNode() {
_codeController.addListener(() {
final otp = _codeController.text;
_ioniaViewModel.otp = otp;
_authViewModel.otp = otp;
if (otp.length > 3) {
_ioniaViewModel.otpState = IoniaOtpSendEnabled();
_authViewModel.otpState = IoniaOtpSendEnabled();
} else {
_ioniaViewModel.otpState = IoniaOtpSendDisabled();
_authViewModel.otpState = IoniaOtpSendDisabled();
}
});
}
final IoniaViewModel _ioniaViewModel;
final IoniaAuthViewModel _authViewModel;
final String _email;
@ -49,7 +50,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.otpState, (IoniaOtpState state) {
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
if (state is IoniaOtpFailure) {
_onOtpFailure(context, state.error);
}
@ -57,57 +58,74 @@ class IoniaVerifyIoniaOtp extends BasePage {
_onOtpSuccessful(context);
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
BaseTextFormField(
hintText: S.of(context).enter_code,
focusNode: _codeFocus,
controller: _codeController,
),
SizedBox(height: 14),
Text(
S.of(context).fill_code,
style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
),
SizedBox(height: 34),
Row(
mainAxisAlignment: MainAxisAlignment.center,
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _codeFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
height: 0,
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
Text(S.of(context).dont_get_code),
SizedBox(width: 20),
InkWell(
onTap: () => _ioniaViewModel.createUser(_email),
child: Text(
S.of(context).resend_code,
style: textSmallSemiBold(color: Palette.blueCraiola),
),
BaseTextFormField(
hintText: S.of(context).enter_code,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
focusNode: _codeFocus,
controller: _codeController,
),
SizedBox(height: 14),
Text(
S.of(context).fill_code,
style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
),
SizedBox(height: 34),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(S.of(context).dont_get_code),
SizedBox(width: 20),
InkWell(
onTap: () => _authViewModel.createUser(_email),
child: Text(
S.of(context).resend_code,
style: textSmallSemiBold(color: Palette.blueCraiola),
),
),
],
),
],
),
],
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
bottomSection: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).continue_text,
onPressed: () async => await _ioniaViewModel.verifyEmail(_codeController.text),
isDisabled: _ioniaViewModel.otpState is IoniaOtpSendDisabled,
isLoading: _ioniaViewModel.otpState is IoniaOtpValidating,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
bottomSection: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).continue_text,
onPressed: () async => await _authViewModel.verifyEmail(_codeController.text),
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
isLoading: _authViewModel.otpState is IoniaOtpValidating,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(height: 20),
],
),
SizedBox(height: 20),
],
),
],
),
),
);
}

View file

@ -3,14 +3,14 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class IoniaWelcomePage extends BasePage {
IoniaWelcomePage(this._ioniaViewModel);
IoniaWelcomePage(this._cardsListViewModel);
@override
Widget middle(BuildContext context) {
@ -22,11 +22,11 @@ class IoniaWelcomePage extends BasePage {
);
}
final IoniaViewModel _ioniaViewModel;
final IoniaGiftCardsListViewModel _cardsListViewModel;
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) {
reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) {
if (state) {
Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage);
}

View file

@ -0,0 +1,118 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
class IoniaAccountCardsPage extends BasePage {
IoniaAccountCardsPage(this.ioniaAccountViewModel);
final IoniaAccountViewModel ioniaAccountViewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.of(context).cards,
style: textLargeSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
return _IoniaCardTabs();
}
}
class _IoniaCardTabs extends StatefulWidget {
@override
_IoniaCardTabsState createState() => _IoniaCardTabsState();
}
class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
@override
void dispose() {
super.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 45,
width: 230,
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(
25.0,
),
),
child: Theme(
data: ThemeData(
primaryTextTheme: TextTheme(
body2: TextStyle(backgroundColor: Colors.transparent)
)
),
child: TabBar(
controller: _tabController,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25.0,
),
color: Theme.of(context).accentTextTheme.body2.color,
),
labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor,
unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color,
tabs: [
Tab(
text: S.of(context).active,
),
Tab(
text: S.of(context).redeemed,
),
],
),
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Center(
child: Text(
S.of(context).gift_card_balance_note,
textAlign: TextAlign.center,
style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,),
),
),
Center(
child: Text(
S.of(context).gift_card_redeemed_note,
textAlign: TextAlign.center,
style: textSmall(color: Theme.of(context).primaryTextTheme.overline.color,),
),
),
],
),
),
],
),
);
}
}

View file

@ -0,0 +1,180 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaAccountPage extends BasePage {
IoniaAccountPage(this.ioniaAccountViewModel);
final IoniaAccountViewModel ioniaAccountViewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.current.account,
style: textLargeSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
final deviceWidth = MediaQuery.of(context).size.width;
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
_GradiantContainer(
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Observer(builder: (_) =>
RichText(
text: TextSpan(
text: '${ioniaAccountViewModel.countOfMerch}',
style: textLargeSemiBold(),
children: [
TextSpan(
text: ' ${S.of(context).active_cards}',
style: textSmall(color: Colors.white.withOpacity(0.7))),
],
),
)),
InkWell(
onTap: () => Navigator.pushNamed(context, Routes.ioniaAccountCardsPage),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
S.of(context).view_all,
style: textSmallSemiBold(),
),
),
)
],
),
),
SizedBox(height: 8),
//Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).total_saving,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '\$100',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).last_30_days,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '\$100',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).avg_savings,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '10%',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// ],
//),
SizedBox(height: 40),
Observer(builder: (_) =>
IoniaTile(
title: S.of(context).email_address,
subTitle: ioniaAccountViewModel.email)),
Divider()
],
),
bottomSectionPadding: EdgeInsets.all(30),
bottomSection: Column(
children: [
PrimaryButton(
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
text: S.of(context).logout,
onPressed: () {
ioniaAccountViewModel.logout();
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
},
),
],
),
);
}
}
class _GradiantContainer extends StatelessWidget {
const _GradiantContainer({
Key key,
@required this.content,
this.padding,
this.width,
}) : super(key: key);
final Widget content;
final EdgeInsets padding;
final double width;
@override
Widget build(BuildContext context) {
return Container(
child: content,
width: width,
padding: padding ?? EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).accentColor,
],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
);
}
}

View file

@ -7,16 +7,16 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class IoniaActivateDebitCardPage extends BasePage {
IoniaActivateDebitCardPage(this._ioniaViewModel);
IoniaActivateDebitCardPage(this._cardsListViewModel);
final IoniaViewModel _ioniaViewModel;
final IoniaGiftCardsListViewModel _cardsListViewModel;
@override
Widget middle(BuildContext context) {
@ -30,7 +30,7 @@ class IoniaActivateDebitCardPage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.createCardState, (IoniaCreateCardState state) {
reaction((_) => _cardsListViewModel.createCardState, (IoniaCreateCardState state) {
if (state is IoniaCreateCardFailure) {
_onCreateCardFailure(context, state.error);
}
@ -72,9 +72,9 @@ class IoniaActivateDebitCardPage extends BasePage {
),
bottomSection: LoadingPrimaryButton(
onPressed: () {
_ioniaViewModel.createCard();
_cardsListViewModel.createCard();
},
isLoading: _ioniaViewModel.createCardState is IoniaCreateCardLoading,
isLoading: _cardsListViewModel.createCardState is IoniaCreateCardLoading,
text: S.of(context).agree_and_continue,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,

View file

@ -1,24 +1,40 @@
import 'dart:ui';
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/discount_badge.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaBuyGiftCardDetailPage extends StatelessWidget {
IoniaBuyGiftCardDetailPage(this.amount, this.ioniaPurchaseViewModel, this.merchant) {
ioniaPurchaseViewModel.setSelectedMerchant(merchant);
}
const IoniaBuyGiftCardDetailPage(this.merchant);
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
final IoniaMerchant merchant;
final String amount;
ThemeBase get currentTheme => getIt.get<SettingsStore>().currentTheme;
Color get backgroundLightColor => Colors.white;
@ -57,148 +73,211 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget {
Widget middle(BuildContext context) {
return Text(
merchant.legalName,
ioniaPurchaseViewModel.ioniaMerchant.legalName,
style: textLargeSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
);
}
@override
Widget build(BuildContext context) {
final merchant = ioniaPurchaseViewModel.ioniaMerchant;
final _backgroundColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor;
reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
reaction((_) => ioniaPurchaseViewModel.invoiceCommittingState, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
if (state is ExecutedSuccessfullyState) {
final transactionInfo = state.payload as AnyPayPaymentCommittedInfo;
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<void>(
context: context,
barrierDismissible: true,
barrierColor: PaletteDark.darkNightBlue.withOpacity(0.75),
builder: (BuildContext context) {
return Center(
child: _IoniaTransactionCommitedAlert(transactionInfo: transactionInfo),
);
},
);
});
}
});
ioniaPurchaseViewModel.onAmountChanged(amount);
return Scaffold(
backgroundColor: _backgroundColor,
body: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Column(
children: [
SizedBox(height: 60),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [leading(context), middle(context), DiscountBadge(percentage: merchant.minimumDiscount,)],
),
SizedBox(height: 36),
Container(
padding: EdgeInsets.symmetric(vertical: 24),
margin: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
content: Observer(builder: (_) {
final tipAmount = ioniaPurchaseViewModel.tipAmount;
return Column(
children: [
SizedBox(height: 60),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
leading(context),
middle(context),
DiscountBadge(
percentage: merchant.minimumDiscount,
)
],
),
SizedBox(height: 36),
Container(
padding: EdgeInsets.symmetric(vertical: 24),
margin: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
Text(
S.of(context).gift_card_amount,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$${ioniaPurchaseViewModel.giftCardAmount}',
style: textXLargeSemiBold(),
),
SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).bill_amount,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$$amount',
style: textLargeSemiBold(),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
S.of(context).tip,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$$tipAmount',
style: textLargeSemiBold(),
),
],
),
],
),
),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
Text(
S.of(context).gift_card_amount,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$1000.12',
style: textXLargeSemiBold(),
),
SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).bill_amount,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$1000.00',
style: textLargeSemiBold(),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
S.of(context).tip,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$1000.00',
style: textLargeSemiBold(),
),
],
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).tip,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontWeight: FontWeight.w700,
fontSize: 14,
),
),
SizedBox(height: 4),
TipButtonGroup(
selectedTip: tipAmount,
tipsList: [
IoniaTip(percentage: 0, originalAmount: double.parse(amount)),
IoniaTip(percentage: 10, originalAmount: double.parse(amount)),
IoniaTip(percentage: 20, originalAmount: double.parse(amount)),
],
),
),
SizedBox(height: 16),
Divider(),
SizedBox(height: 16),
Text(
S.of(context).you_pay,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'22.3435345000 XMR',
style: textLargeSemiBold(),
),
],
onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
)
],
),
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).tip,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
fontWeight: FontWeight.w700,
fontSize: 14,
),
),
SizedBox(height: 4),
TipButtonGroup()
],
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: TextIconButton(
label: S.of(context).how_to_use_card,
onTap: () => _showHowToUseCard(context, merchant),
),
),
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: TextIconButton(
label: S.of(context).how_to_use_card,
onTap: () {},
),
),
],
),
],
);
}),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () => purchaseCard(context),
text: S.of(context).purchase_gift_card,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
child: Observer(builder: (_) {
return LoadingPrimaryButton(
isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState ||
ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState,
isDisabled: !ioniaPurchaseViewModel.enableCardPurchase,
onPressed: () => purchaseCard(context),
text: S.of(context).purchase_gift_card,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
);
}),
),
SizedBox(height: 8),
Text(S.of(context).settings_terms_and_conditions,
style: textMediumSemiBold(
color: Theme.of(context).primaryTextTheme.body1.color,
).copyWith(fontSize: 12)),
InkWell(
onTap: () => _showTermsAndCondition(context),
child: Text(S.of(context).settings_terms_and_conditions,
style: textMediumSemiBold(
color: Theme.of(context).primaryTextTheme.body1.color,
).copyWith(fontSize: 12)),
),
SizedBox(height: 16)
],
),
@ -206,117 +285,351 @@ class IoniaBuyGiftCardDetailPage extends StatelessWidget {
);
}
void purchaseCard(BuildContext context) {
void _showTermsAndCondition(BuildContext context) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: '',
alertContent: merchant.termsAndConditions,
buttonText: S.of(context).agree,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
Future<void> purchaseCard(BuildContext context) async {
await ioniaPurchaseViewModel.createInvoice();
if (ioniaPurchaseViewModel.invoiceCreationState is ExecutedSuccessfullyState) {
await _presentSuccessfulInvoiceCreationPopup(context);
}
}
void _showHowToUseCard(
BuildContext context,
IoniaMerchant merchant,
) {
showPopUp<void>(
context: context,
builder: (_) {
return IoniaConfirmModal(
alertTitle: S.of(context).confirm_sending,
alertContent: SizedBox(
//Todo:: substitute this widget with modal content
height: 200,
builder: (BuildContext context) {
return AlertBackground(
child: Material(
color: Colors.transparent,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 10),
Container(
padding: EdgeInsets.only(top: 24, left: 24, right: 24),
margin: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(30),
),
child: Column(
children: [
Text(
S.of(context).how_to_use_card,
style: textLargeSemiBold(
color: Theme.of(context).textTheme.body1.color,
),
),
SizedBox(height: 24),
Align(
alignment: Alignment.bottomLeft,
child: Text(
merchant.usageInstructionsBak,
style: textMedium(
color: Theme.of(context).textTheme.display2.color,
),
),
),
SizedBox(height: 35),
PrimaryButton(
onPressed: () => Navigator.pop(context),
text: S.of(context).send_got_it,
color: Color.fromRGBO(233, 242, 252, 1),
textColor: Theme.of(context).textTheme.display2.color,
),
SizedBox(height: 21),
],
),
),
InkWell(
onTap: () => Navigator.pop(context),
child: Container(
margin: EdgeInsets.only(bottom: 40),
child: CircleAvatar(
child: Icon(
Icons.close,
color: Colors.black,
),
backgroundColor: Colors.white,
),
),
)
],
),
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
leftActionColor: Color(0xffFF6600),
rightActionColor: Theme.of(context).accentTextTheme.body2.color,
actionRightButton: () async {
Navigator.of(context).pop();
},
actionLeftButton: () => Navigator.of(context).pop());
),
);
});
}
Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
final amount = ioniaPurchaseViewModel.invoice.totalAmount;
final addresses = ioniaPurchaseViewModel.invoice.outAddresses;
await showPopUp<void>(
context: context,
builder: (_) {
return IoniaConfirmModal(
alertTitle: S.of(context).confirm_sending,
alertContent: Container(
height: 200,
padding: EdgeInsets.all(15),
child: Column(children: [
Row(children: [
Text(S.of(context).payment_id,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none)),
Text(ioniaPurchaseViewModel.invoice.paymentId,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
SizedBox(height: 10),
Row(children: [
Text(S.of(context).amount,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none)),
Text('$amount ${ioniaPurchaseViewModel.invoice.chain}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
SizedBox(height: 25),
Row(children: [
Text(S.of(context).recipient_address,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.center),
Expanded(
child: ListView.builder(
itemBuilder: (_, int index) {
return Text(addresses[index],
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none));
},
itemCount: addresses.length,
physics: NeverScrollableScrollPhysics()))
])),
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
leftActionColor: Color(0xffFF6600),
rightActionColor: Theme.of(context).accentTextTheme.body2.color,
actionRightButton: () async {
Navigator.of(context).pop();
await ioniaPurchaseViewModel.commitPaymentInvoice();
},
actionLeftButton: () => Navigator.of(context).pop());
},
);
}
}
class TipButtonGroup extends StatefulWidget {
const TipButtonGroup({
class _IoniaTransactionCommitedAlert extends StatelessWidget {
const _IoniaTransactionCommitedAlert({
Key key,
@required this.transactionInfo,
}) : super(key: key);
final AnyPayPaymentCommittedInfo transactionInfo;
@override
_TipButtonGroupState createState() => _TipButtonGroupState();
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
width: 327,
height: 340,
color: Theme.of(context).accentTextTheme.title.decorationColor,
child: Material(
color: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.fromLTRB(40, 20, 40, 0),
child: Text(
S.of(context).awaiting_payment_confirmation,
textAlign: TextAlign.center,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
),
),
Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).transaction_sent,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
).copyWith(fontWeight: FontWeight.w500),
),
SizedBox(height: 20),
Text(
S.of(context).transaction_sent_notice,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
).copyWith(fontWeight: FontWeight.w500),
),
],
),
),
Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
StandartListRow(
title: '${S.current.transaction_details_transaction_id}:',
value: transactionInfo.chain,
),
StandartListRow(
title: '${S.current.view_in_block_explorer}:',
value: '${S.current.view_transaction_on} XMRChain.net'),
],
),
),
),
);
}
}
class _TipButtonGroupState extends State<TipButtonGroup> {
String selectedTip;
class TipButtonGroup extends StatelessWidget {
const TipButtonGroup({
Key key,
@required this.selectedTip,
@required this.onSelect,
@required this.tipsList,
}) : super(key: key);
final Function(IoniaTip) onSelect;
final double selectedTip;
final List<IoniaTip> tipsList;
bool _isSelected(String value) {
return selectedTip == value;
final tip = selectedTip.round().toString();
return tip == value;
}
@override
Widget build(BuildContext context) {
return Row(
children: [
TipButton(
isSelected: _isSelected('299'),
caption: '\$10',
subTitle: '%299',
),
SizedBox(width: 4),
TipButton(
caption: '\$10',
subTitle: '%299',
),
SizedBox(width: 4),
TipButton(
isSelected: _isSelected('299'),
caption: '\$10',
subTitle: '%299',
),
SizedBox(width: 4),
TipButton(
isSelected: _isSelected('299'),
caption: S.of(context).custom,
),
...[
for (var i = 0; i < tipsList.length; i++) ...[
TipButton(
isSelected: _isSelected(tipsList[i].originalAmount.toString()),
onTap: () => onSelect(tipsList[i]),
caption: '${tipsList[i].percentage}%',
subTitle: '\$${tipsList[i].additionalAmount}',
),
SizedBox(width: 4),
]
],
],
);
}
}
class TipButton extends StatelessWidget {
final String caption;
final String subTitle;
final bool isSelected;
final void Function(int, bool) onTap;
const TipButton({
@required this.caption,
this.subTitle,
this.onTap,
@required this.onTap,
this.isSelected = false,
});
final String caption;
final String subTitle;
final bool isSelected;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return Container(
height: 49,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(caption, style: textSmallSemiBold(color: Theme.of(context).primaryTextTheme.title.color)),
if (subTitle != null) ...[
SizedBox(height: 4),
Text(
subTitle,
style: textXxSmallSemiBold(color: Palette.gray),
),
]
],
),
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromRGBO(242, 240, 250, 1),
gradient: isSelected
? LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
return InkWell(
onTap: onTap,
child: Container(
height: 49,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(caption,
style: textSmallSemiBold(
color: isSelected
? Theme.of(context).accentTextTheme.title.color
: Theme.of(context).primaryTextTheme.title.color)),
if (subTitle != null) ...[
SizedBox(height: 4),
Text(
subTitle,
style: textXxSmallSemiBold(
color: isSelected
? Theme.of(context).accentTextTheme.title.color
: Theme.of(context).primaryTextTheme.overline.color,
),
),
]
],
),
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromRGBO(242, 240, 250, 1),
gradient: isSelected
? LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
),
),
);
}

View file

@ -7,18 +7,28 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/generated/i18n.dart';
class IoniaBuyGiftCardPage extends BasePage {
IoniaBuyGiftCardPage(this.merchant)
: _amountFieldFocus = FocusNode(),
_amountController = TextEditingController();
IoniaBuyGiftCardPage(
this.ioniaPurchaseViewModel, this.merchant,
) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() {
ioniaPurchaseViewModel.setSelectedMerchant(merchant);
_amountController.addListener(() {
ioniaPurchaseViewModel.onAmountChanged(_amountController.text);
});
}
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
final IoniaMerchant merchant;
@override
String get title => S.current.enter_amount;
@ -39,6 +49,7 @@ class IoniaBuyGiftCardPage extends BasePage {
@override
Widget body(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final merchant = ioniaPurchaseViewModel.ioniaMerchant;
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
@ -98,7 +109,7 @@ class IoniaBuyGiftCardPage extends BasePage {
left: _width / 4,
),
child: Text(
'${merchant.acceptedCurrency}: ',
'USD: ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
@ -146,20 +157,27 @@ class IoniaBuyGiftCardPage extends BasePage {
),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () {
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage, arguments: [merchant] );
},
text: S.of(context).continue_text,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Theme.of(context)
Observer(builder: (_) {
return Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(
Routes.ioniaBuyGiftCardDetailPage,
arguments: [
ioniaPurchaseViewModel.amount,
ioniaPurchaseViewModel.ioniaMerchant,
],
),
text: S.of(context).continue_text,
isDisabled: !ioniaPurchaseViewModel.enableCardPurchase,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Theme.of(context)
.accentTextTheme
.headline
.decorationColor,
),
),
),
);
}),
SizedBox(height: 30),
],
),

View file

@ -0,0 +1,181 @@
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/generated/i18n.dart';
class IoniaCustomTipPage extends BasePage {
IoniaCustomTipPage(
this.ioniaPurchaseViewModel,
this.billAmount,
this.merchant,
) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() {
ioniaPurchaseViewModel.setSelectedMerchant(merchant);
ioniaPurchaseViewModel.onAmountChanged(billAmount);
_amountController.addListener(() {
// ioniaPurchaseViewModel.onTipChanged(_amountController.text);
});
}
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
final String billAmount;
final IoniaMerchant merchant;
@override
String get title => S.current.enter_amount;
@override
Color get titleColor => Colors.white;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
final TextEditingController _amountController;
final FocusNode _amountFieldFocus;
@override
Widget body(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final merchant = ioniaPurchaseViewModel.ioniaMerchant;
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFieldFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 150),
BaseTextFormField(
controller: _amountController,
focusNode: _amountFieldFocus,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))],
hintText: '1000',
placeholderTextStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
fontWeight: FontWeight.w500,
fontSize: 36,
),
borderColor: Theme.of(context).primaryTextTheme.headline.color,
textColor: Colors.white,
textStyle: TextStyle(
color: Colors.white,
fontSize: 36,
),
suffixIcon: SizedBox(
width: _width / 6,
),
prefixIcon: Padding(
padding: EdgeInsets.only(
top: 5.0,
left: _width / 4,
),
child: Text(
'USD: ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 36,
),
),
),
),
SizedBox(height: 8),
Observer(builder: (_) {
if (ioniaPurchaseViewModel.percentage == 0.0) {
return SizedBox.shrink();
}
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: '\$${_amountController.text}',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
),
children: [
TextSpan(text: ' ${S.of(context).is_percentage} '),
TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'),
TextSpan(text: ' ${S.of(context).percentageOf(billAmount)} '),
],
),
);
}),
SizedBox(height: 24),
],
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: CardItem(
title: merchant.legalName,
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
discount: 0.0,
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
subtitleColor: Theme.of(context).hintColor,
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
logoUrl: merchant.logoUrl,
),
)
],
),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () {
Navigator.of(context).pop(_amountController.text);
},
text: S.of(context).add_tip,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(height: 30),
],
),
),
),
);
}
}

View file

@ -8,15 +8,15 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaDebitCardPage extends BasePage {
final IoniaViewModel _ioniaViewModel;
final IoniaGiftCardsListViewModel _cardsListViewModel;
IoniaDebitCardPage(this._ioniaViewModel);
IoniaDebitCardPage(this._cardsListViewModel);
@override
Widget middle(BuildContext context) {
@ -32,7 +32,7 @@ class IoniaDebitCardPage extends BasePage {
Widget body(BuildContext context) {
return Observer(
builder: (_) {
final cardState = _ioniaViewModel.cardState;
final cardState = _cardsListViewModel.cardState;
if (cardState is IoniaFetchingCard) {
return Center(child: CircularProgressIndicator());
}

View file

@ -1,20 +1,37 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart';
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaManageCardsPage extends BasePage {
IoniaManageCardsPage(this._ioniaViewModel);
final IoniaViewModel _ioniaViewModel;
IoniaManageCardsPage(this._cardsListViewModel) {
_searchController.addListener(() {
if (_searchController.text != _cardsListViewModel.searchString) {
_searchDebounce.run(() {
_cardsListViewModel.searchMerchant(_searchController.text);
});
}
});
}
final IoniaGiftCardsListViewModel _cardsListViewModel;
final _searchDebounce = Debounce(Duration(milliseconds: 500));
final _searchController = TextEditingController();
@override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@ -80,18 +97,25 @@ class IoniaManageCardsPage extends BasePage {
);
}
@override
Widget trailing(BuildContext context) {
return
_TrailingIcon(
asset: 'assets/images/profile.png',
onPressed: () {},
return _TrailingIcon(
asset: 'assets/images/profile.png',
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
);
}
@override
Widget body(BuildContext context) {
final filterIcon = InkWell(
onTap: () async {
final selectedFilters = await showCategoryFilter(context, _cardsListViewModel);
_cardsListViewModel.setSelectedFilter(selectedFilters);
},
child: Image.asset(
'assets/images/filter.png',
color: Theme.of(context).textTheme.caption.decorationColor,
));
return Padding(
padding: const EdgeInsets.all(14.0),
@ -100,103 +124,134 @@ class IoniaManageCardsPage extends BasePage {
Container(
padding: EdgeInsets.only(left: 2, right: 22),
height: 32,
child: _SearchWidget()
child: Row(
children: [
Expanded(
child: _SearchWidget(
controller: _searchController,
)),
SizedBox(width: 10),
Container(
width: 32,
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
border: Border.all(
color: Colors.white.withOpacity(0.2),
),
borderRadius: BorderRadius.circular(10),
),
child: filterIcon,
)
],
),
),
SizedBox(height: 8),
Expanded(
child: Observer(builder: (_) {
return IoniaManageCardsPageBody(scrollOffsetFromTop: _ioniaViewModel.scrollOffsetFromTop,
ioniaMerchants: _ioniaViewModel.ioniaMerchants,
onSetScrollOffset: (offset) => _ioniaViewModel.setScrollOffsetFromTop(offset),
);
}),
child: IoniaManageCardsPageBody(
cardsListViewModel: _cardsListViewModel,
),
),
],
),
);
}
Future<List<IoniaCategory>> showCategoryFilter(
BuildContext context,
IoniaGiftCardsListViewModel viewModel,
) async {
return await showPopUp<List<IoniaCategory>>(
context: context,
builder: (BuildContext context) {
return IoniaFilterModal(
filterViewModel: getIt.get<IoniaFilterViewModel>(),
selectedCategories: viewModel.selectedFilters,
);
},
);
}
}
class IoniaManageCardsPageBody extends StatefulWidget {
const IoniaManageCardsPageBody({
Key key,
@required this.scrollOffsetFromTop,
@required this.ioniaMerchants,
@required this.onSetScrollOffset,
@required this.cardsListViewModel,
}) : super(key: key);
final List<IoniaMerchant> ioniaMerchants;
final double scrollOffsetFromTop;
final Function(double) onSetScrollOffset;
final IoniaGiftCardsListViewModel cardsListViewModel;
@override
_IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
}
class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
double thumbHeight = 72;
bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
double thumbHeight = 72;
bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
List<IoniaMerchant> get merchantsList => widget.ioniaMerchants;
List<IoniaMerchant> get merchantsList => widget.cardsListViewModel.ioniaMerchants;
final _scrollController = ScrollController();
@override
@override
void initState() {
_scrollController.addListener(() {
final scrollOffsetFromTop = _scrollController.hasClients
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
: 0.0;
widget.onSetScrollOffset(scrollOffsetFromTop);
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(children: [
ListView.separated(
padding: EdgeInsets.only(left: 2, right: 22),
controller: _scrollController,
itemCount: merchantsList.length,
separatorBuilder: (_, __) => SizedBox(height: 4),
itemBuilder: (_, index) {
final merchant = merchantsList[index];
return CardItem(
logoUrl: merchant.logoUrl,
onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]),
title: merchant.legalName,
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
backgroundColor: Theme.of(context).textTheme.title.backgroundColor,
titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor,
subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor,
discount: merchant.minimumDiscount,
);
},
),
isAlwaysShowScrollThumb
? CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
rightOffset: 1,
width: 3,
backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05),
thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5),
fromTop: widget.scrollOffsetFromTop,
)
: Offstage()
]);
return Observer(
builder: (_) => Stack(children: [
ListView.separated(
padding: EdgeInsets.only(left: 2, right: 22),
controller: _scrollController,
itemCount: merchantsList.length,
separatorBuilder: (_, __) => SizedBox(height: 4),
itemBuilder: (_, index) {
final merchant = merchantsList[index];
return CardItem(
logoUrl: merchant.logoUrl,
onTap: () {
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
},
title: merchant.legalName,
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
backgroundColor: Theme.of(context).textTheme.title.backgroundColor,
titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor,
subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor,
discount: merchant.minimumDiscount,
);
},
),
isAlwaysShowScrollThumb
? CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
rightOffset: 1,
width: 3,
backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05),
thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5),
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
)
: Offstage()
]),
);
}
}
class _SearchWidget extends StatelessWidget {
const _SearchWidget({
Key key,
@required this.controller,
}) : super(key: key);
final TextEditingController controller;
@override
Widget build(BuildContext context) {
@ -210,6 +265,7 @@ class _SearchWidget extends StatelessWidget {
return TextField(
style: TextStyle(color: Colors.white),
controller: controller,
decoration: InputDecoration(
filled: true,
contentPadding: EdgeInsets.only(
@ -268,5 +324,3 @@ class _TrailingIcon extends StatelessWidget {
);
}
}

View file

@ -13,6 +13,7 @@ class IoniaConfirmModal extends StatelessWidget {
@required this.actionRightButton,
this.leftActionColor,
this.rightActionColor,
this.hideActions = false,
});
final String alertTitle;
@ -23,6 +24,7 @@ class IoniaConfirmModal extends StatelessWidget {
final VoidCallback actionRightButton;
final Color leftActionColor;
final Color rightActionColor;
final bool hideActions;
Widget actionButtons(BuildContext context) {
return Row(

View file

@ -0,0 +1,134 @@
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaFilterModal extends StatelessWidget {
IoniaFilterModal({
@required this.filterViewModel,
@required this.selectedCategories,
}) {
filterViewModel.setSelectedCategories(this.selectedCategories);
}
final IoniaFilterViewModel filterViewModel;
final List<IoniaCategory> selectedCategories;
@override
Widget build(BuildContext context) {
final searchIcon = Padding(
padding: EdgeInsets.all(10),
child: Image.asset(
'assets/images/search_icon.png',
color: Theme.of(context).accentColor,
),
);
return Scaffold(
resizeToAvoidBottomInset: false,
body: AlertBackground(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 10),
Container(
padding: EdgeInsets.only(top: 24, bottom: 20),
margin: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(30),
),
child: Column(
children: [
SizedBox(
height: 40,
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: TextField(
onChanged: filterViewModel.onSearchFilter,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
),
decoration: InputDecoration(
filled: true,
prefixIcon: searchIcon,
hintText: S.of(context).search_category,
contentPadding: EdgeInsets.only(bottom: 5),
fillColor: Theme.of(context).textTheme.subhead.backgroundColor,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
SizedBox(height: 10),
Divider(thickness: 2),
SizedBox(height: 24),
Observer(builder: (_) {
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: filterViewModel.ioniaCategories.length,
itemBuilder: (_, index) {
final category = filterViewModel.ioniaCategories[index];
return Padding(
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: InkWell(
onTap: () => filterViewModel.selectFilter(category),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
category.iconPath,
color: Theme.of(context).primaryTextTheme.title.color,
),
SizedBox(width: 10),
Text(category.title,
style: textSmall(
color: Theme.of(context).primaryTextTheme.title.color,
).copyWith(fontWeight: FontWeight.w500)),
],
),
Observer(builder: (_) {
final value = filterViewModel.selectedIndices;
return RoundedCheckbox(
value: value.contains(category.index),
);
}),
],
),
),
);
},
);
}),
],
),
),
InkWell(
onTap: () => Navigator.pop(context, filterViewModel.selectedCategories),
child: Container(
margin: EdgeInsets.only(bottom: 40),
child: CircleAvatar(
child: Icon(
Icons.close,
color: Colors.black,
),
backgroundColor: Colors.white,
),
),
)
],
),
),
);
}
}

View file

@ -0,0 +1,58 @@
import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart';
class IoniaTile extends StatelessWidget {
const IoniaTile({
Key key,
@required this.title,
@required this.subTitle,
this.trailing,
this.onTapTrailing,
}) : super(key: key);
final Widget trailing;
final VoidCallback onTapTrailing;
final String title;
final String subTitle;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textXSmall(
color: Theme.of(context).primaryTextTheme.overline.color,
),
),
SizedBox(height: 8),
Text(
subTitle,
style: textMediumBold(
color: Theme.of(context).primaryTextTheme.title.color,
),
),
],
),
trailing != null
? InkWell(
onTap: () => onTapTrailing,
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 6),
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4)),
child: trailing,
),
),
)
: Offstage(),
],
);
}
}

View file

@ -0,0 +1,27 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RoundedCheckbox extends StatelessWidget {
RoundedCheckbox({Key key, @required this.value}) : super(key: key);
final bool value;
@override
Widget build(BuildContext context) {
return value
? Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
color: Theme.of(context).accentTextTheme.body2.color,
),
child: Icon(
Icons.check,
color: Theme.of(context).backgroundColor,
size: 14.0,
))
: Offstage();
}
}

View file

@ -0,0 +1,36 @@
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'ionia_account_view_model.g.dart';
class IoniaAccountViewModel = IoniaAccountViewModelBase with _$IoniaAccountViewModel;
abstract class IoniaAccountViewModelBase with Store {
IoniaAccountViewModelBase({this.ioniaService}) {
email = '';
merchs = [];
ioniaService.getUserEmail()
.then((email) => this.email = email);
ioniaService.getCurrentUserGiftCardSummaries()
.then((merchs) => this.merchs = merchs);
}
final IoniaService ioniaService;
@observable
String email;
@observable
List<IoniaMerchant> merchs;
@computed
int get countOfMerch => merchs.where((merch) => merch.isActive).length;
@action
void logout(){
ioniaService.logout();
}
}

View file

@ -0,0 +1,55 @@
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'ionia_auth_view_model.g.dart';
class IoniaAuthViewModel = IoniaAuthViewModelBase with _$IoniaAuthViewModel;
abstract class IoniaAuthViewModelBase with Store {
IoniaAuthViewModelBase({this.ioniaService}):
createUserState = IoniaCreateStateSuccess(),
otpState = IoniaOtpSendDisabled(){
}
final IoniaService ioniaService;
@observable
IoniaCreateAccountState createUserState;
@observable
IoniaOtpState otpState;
@observable
String email;
@observable
String otp;
@action
Future<void> verifyEmail(String code) async {
try {
otpState = IoniaOtpValidating();
await ioniaService.verifyEmail(code);
otpState = IoniaOtpSuccess();
} catch (_) {
otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again');
}
}
@action
Future<void> createUser(String email) async {
createUserState = IoniaCreateStateLoading();
try {
await ioniaService.createUser(email);
createUserState = IoniaCreateStateSuccess();
} on Exception catch (e) {
createUserState = IoniaCreateStateFailure(error: e.toString());
}
}
}

View file

@ -0,0 +1,58 @@
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:mobx/mobx.dart';
part 'ionia_filter_view_model.g.dart';
class IoniaFilterViewModel = IoniaFilterViewModelBase with _$IoniaFilterViewModel;
abstract class IoniaFilterViewModelBase with Store {
IoniaFilterViewModelBase() {
selectedIndices = ObservableList<int>();
ioniaCategories = IoniaCategory.allCategories;
}
List<IoniaCategory> get selectedCategories => ioniaCategories.where(_isSelected).toList();
@observable
ObservableList<int> selectedIndices;
@observable
List<IoniaCategory> ioniaCategories;
@action
void selectFilter(IoniaCategory ioniaCategory) {
if (ioniaCategory == IoniaCategory.all && !selectedIndices.contains(0)) {
selectedIndices.clear();
selectedIndices.add(0);
return;
}
if (selectedIndices.contains(ioniaCategory.index) && ioniaCategory.index != 0) {
selectedIndices.remove(ioniaCategory.index);
return;
}
selectedIndices.add(ioniaCategory.index);
selectedIndices.remove(0);
}
@action
void onSearchFilter(String text) {
if (text.isEmpty) {
ioniaCategories = IoniaCategory.allCategories;
} else {
ioniaCategories = IoniaCategory.allCategories
.where(
(e) => e.title.toLowerCase().contains(text.toLowerCase()),
)
.toList();
}
}
@action
void setSelectedCategories(List<IoniaCategory> selectedCategories) {
selectedIndices = ObservableList.of(selectedCategories.map((e) => e.index));
}
bool _isSelected(IoniaCategory ioniaCategory) {
return selectedIndices.contains(ioniaCategory.index);
}
}

View file

@ -1,36 +1,38 @@
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
part 'ionia_view_model.g.dart';
part 'ionia_gift_cards_list_view_model.g.dart';
class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel;
class IoniaGiftCardsListViewModel = IoniaGiftCardsListViewModelBase with _$IoniaGiftCardsListViewModel;
abstract class IoniaViewModelBase with Store {
IoniaViewModelBase({this.ioniaService})
: createUserState = IoniaCreateStateSuccess(),
otpState = IoniaOtpSendDisabled(),
abstract class IoniaGiftCardsListViewModelBase with Store {
IoniaGiftCardsListViewModelBase({
@required this.ioniaService,
}) :
cardState = IoniaNoCardState(),
ioniaMerchants = [],
scrollOffsetFromTop = 0.0 {
_getMerchants().then((value) {
ioniaMerchants = value;
});
_getAuthStatus().then((value) => isLoggedIn = value);
selectedFilters = [];
_getAuthStatus().then((value) => isLoggedIn = value);
_getMerchants();
}
final IoniaService ioniaService;
List<IoniaMerchant> ioniaMerchantList;
String searchString;
List<IoniaCategory> selectedFilters;
@observable
double scrollOffsetFromTop;
@observable
IoniaCreateAccountState createUserState;
@observable
IoniaOtpState otpState;
@observable
IoniaCreateCardState createCardState;
@ -40,38 +42,9 @@ abstract class IoniaViewModelBase with Store {
@observable
List<IoniaMerchant> ioniaMerchants;
@observable
String email;
@observable
String otp;
@observable
bool isLoggedIn;
@action
Future<void> createUser(String email) async {
createUserState = IoniaCreateStateLoading();
try {
await ioniaService.createUser(email);
createUserState = IoniaCreateStateSuccess();
} on Exception catch (e) {
createUserState = IoniaCreateStateFailure(error: e.toString());
}
}
@action
Future<void> verifyEmail(String code) async {
try {
otpState = IoniaOtpValidating();
await ioniaService.verifyEmail(code);
otpState = IoniaOtpSuccess();
} catch (_) {
otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again');
}
}
Future<bool> _getAuthStatus() async {
return await ioniaService.isLogined();
}
@ -89,6 +62,18 @@ abstract class IoniaViewModelBase with Store {
return null;
}
@action
void searchMerchant(String text) {
if (text.isEmpty) {
ioniaMerchants = ioniaMerchantList;
return;
}
searchString = text;
ioniaService.getMerchantsByFilter(search: searchString).then((value) {
ioniaMerchants = value;
});
}
Future<void> _getCard() async {
cardState = IoniaFetchingCard();
try {
@ -100,8 +85,16 @@ abstract class IoniaViewModelBase with Store {
}
}
Future<List<IoniaMerchant>> _getMerchants() async {
return await ioniaService.getMerchants();
void _getMerchants() {
ioniaService.getMerchantsByFilter(categories: selectedFilters).then((value) {
ioniaMerchants = ioniaMerchantList = value;
});
}
@action
void setSelectedFilter(List<IoniaCategory> filters) {
selectedFilters = filters;
_getMerchants();
}
void setScrollOffsetFromTop(double scrollOffset) {

View file

@ -0,0 +1,91 @@
import 'package:cake_wallet/anypay/any_pay_payment.dart';
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:mobx/mobx.dart';
part 'ionia_purchase_merch_view_model.g.dart';
class IoniaMerchPurchaseViewModel = IoniaMerchPurchaseViewModelBase with _$IoniaMerchPurchaseViewModel;
abstract class IoniaMerchPurchaseViewModelBase with Store {
IoniaMerchPurchaseViewModelBase(this.ioniaAnyPayService) {
tipAmount = 0.0;
percentage = 0.0;
amount = '';
enableCardPurchase = false;
}
IoniaMerchant ioniaMerchant;
IoniaAnyPay ioniaAnyPayService;
AnyPayPayment invoice;
AnyPayPaymentCommittedInfo committedInfo;
@observable
ExecutionState invoiceCreationState;
@observable
ExecutionState invoiceCommittingState;
@observable
String amount;
@observable
double percentage;
@computed
double get giftCardAmount => double.parse(amount) + tipAmount;
@observable
double tipAmount;
@observable
bool enableCardPurchase;
@action
void onAmountChanged(String input) {
if (input.isEmpty) return;
amount = input;
final inputAmount = double.parse(input);
final min = ioniaMerchant.minimumCardPurchase;
final max = ioniaMerchant.maximumCardPurchase;
enableCardPurchase = inputAmount >= min && inputAmount <= max;
}
void setSelectedMerchant(IoniaMerchant merchant) {
ioniaMerchant = merchant;
}
@action
void addTip(IoniaTip tip) {
tipAmount = tip.additionalAmount;
}
@action
Future<void> createInvoice() async {
try {
invoiceCreationState = IsExecutingState();
invoice = await ioniaAnyPayService.purchase(merchId: ioniaMerchant.id.toString(), amount: giftCardAmount);
invoiceCreationState = ExecutedSuccessfullyState();
} catch (e) {
invoiceCreationState = FailureState(e.toString());
}
}
@action
Future<void> commitPaymentInvoice() async {
try {
invoiceCommittingState = IsExecutingState();
committedInfo = await ioniaAnyPayService.commitInvoice(invoice);
invoiceCommittingState = ExecutedSuccessfullyState(payload: committedInfo);
} catch (e) {
invoiceCommittingState = FailureState(e.toString());
}
}
}

View file

@ -596,5 +596,28 @@
"mm": "MM",
"yy": "YY",
"online": "Online",
"offline": "Offline"
"offline": "Offline",
"gift_card_number": "Gift card number",
"pin_number": "PIN number",
"total_saving": "Total Savings",
"last_30_days": "Last 30 days",
"avg_savings": "Avg. savings",
"view_all": "View all",
"active_cards": "Active cards",
"delete_account": "Delete Account",
"cards": "Cards",
"active": "Active",
"redeemed": "Redeemed",
"gift_card_balance_note": "Gift cards with a balance remaining will appear here",
"gift_card_redeemed_note": "Gift cards youve redeemed will appear here",
"logout": "Logout",
"add_tip": "Add Tip",
"percentageOf": "of ${amount}",
"is_percentage": "is",
"search_category": "Search category",
"mark_as_redeemed": "Mark As Redeemed",
"more_options": "More Options",
"awaiting_payment_confirmation": "Awaiting payment confirmation",
"transaction_sent_notice": "If the screen doesnt proceed after 1 minute, check a block explorer and your email.",
"agree": "Agree"
}