Ionia manage card UI (#374)
* design ui for cakepay * Add manage cards page ui * create auth ui for ionia * add authentication logic * implement user create card
BIN
assets/images/badge_discount.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/card.png
Normal file
After Width: | Height: | Size: 556 B |
BIN
assets/images/filter.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
assets/images/mastercard.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/profile.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
assets/images/search_icon.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
assets/images/wifi.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -169,13 +169,6 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
cw_monero:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_monero"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
11
lib/core/email_validator.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class EmailValidator extends TextValidator {
|
||||
EmailValidator()
|
||||
: super(
|
||||
errorMessage: 'Invalid email address',
|
||||
pattern:
|
||||
'^[^@]+@[^@]+\.[^@]+',
|
||||
);
|
||||
}
|
35
lib/di.dart
|
@ -1,11 +1,15 @@
|
|||
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.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_api.dart';
|
||||
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/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:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
|
@ -641,5 +645,36 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => AddressResolver(yatService: getIt.get<YatService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaApi());
|
||||
|
||||
getIt.registerFactory<IoniaService>(
|
||||
() => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaCreateAccountPage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
|
||||
final email = args.first as String;
|
||||
final ioniaViewModel = args[1] as IoniaViewModel;
|
||||
|
||||
return IoniaVerifyIoniaOtp(ioniaViewModel, email);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => IoniaWelcomePage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaBuyGiftCardPage());
|
||||
|
||||
getIt.registerFactory(() => IoniaBuyGiftCardDetailPage());
|
||||
|
||||
getIt.registerFactory(() => IoniaManageCardsPage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaDebitCardPage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaActivateDebitCardPage(getIt.get<IoniaViewModel>()));
|
||||
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ class IoniaApi {
|
|||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['ErrorMessage'] as String);
|
||||
throw Exception(data['message'] as String);
|
||||
}
|
||||
|
||||
final virtualCard = data['VirtualCard'] as Map<String, Object>;
|
||||
|
@ -117,7 +117,7 @@ class IoniaApi {
|
|||
final isSuccessful = bodyJson['Successful'] as bool;
|
||||
|
||||
if (!isSuccessful) {
|
||||
throw Exception(data['ErrorMessage'] as String);
|
||||
throw Exception(data['message'] as String);
|
||||
}
|
||||
|
||||
return IoniaVirtualCard.fromMap(data);
|
||||
|
|
56
lib/ionia/ionia_create_state.dart
Normal file
|
@ -0,0 +1,56 @@
|
|||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateSuccess extends IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateLoading extends IoniaCreateAccountState {}
|
||||
|
||||
class IoniaCreateStateFailure extends IoniaCreateAccountState {
|
||||
IoniaCreateStateFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
abstract class IoniaOtpState {}
|
||||
|
||||
class IoniaOtpValidating extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSuccess extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSendDisabled extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpSendEnabled extends IoniaOtpState {}
|
||||
|
||||
class IoniaOtpFailure extends IoniaOtpState {
|
||||
IoniaOtpFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardSuccess extends IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardLoading extends IoniaCreateCardState {}
|
||||
|
||||
class IoniaCreateCardFailure extends IoniaCreateCardState {
|
||||
IoniaCreateCardFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class IoniaFetchCardState {}
|
||||
|
||||
class IoniaNoCardState extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaFetchingCard extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaFetchCardFailure extends IoniaFetchCardState {}
|
||||
|
||||
class IoniaCardSuccess extends IoniaFetchCardState {
|
||||
IoniaCardSuccess({@required this.card});
|
||||
|
||||
final IoniaVirtualCard card;
|
||||
}
|
|
@ -70,6 +70,7 @@ import 'package:hive/hive.dart';
|
|||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/ionia.dart';
|
||||
|
||||
RouteSettings currentRouteSettings;
|
||||
|
||||
|
@ -402,6 +403,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
getIt.get<UnspentCoinsDetailsPage>(
|
||||
param1: args));
|
||||
|
||||
case Routes.ioniaWelcomePage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaWelcomePage>());
|
||||
|
||||
case Routes.ioniaLoginPage:
|
||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
|
||||
|
||||
case Routes.ioniaCreateAccountPage:
|
||||
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
|
||||
|
||||
case Routes.ioniaManageCardsPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
|
||||
|
||||
case Routes.ioniaBuyGiftCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>());
|
||||
|
||||
case Routes.ioniaBuyGiftCardDetailPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>());
|
||||
|
||||
case Routes.ioniaVerifyIoniaOtpPage:
|
||||
final args = settings.arguments as List;
|
||||
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
|
||||
|
||||
case Routes.ioniaDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
|
||||
|
||||
case Routes.ioniaActivateDebitCardPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaActivateDebitCardPage>());
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -60,4 +60,14 @@ class Routes {
|
|||
static const moneroRestoreWalletFromWelcome = '/monero_restore_wallet';
|
||||
static const moneroNewWalletFromWelcome = '/monero_new_wallet';
|
||||
static const addressPage = '/address_page';
|
||||
}
|
||||
static const ioniaWelcomePage = '/cake_pay_welcome_page';
|
||||
static const ioniaCreateAccountPage = '/cake_pay_create_account_page';
|
||||
static const ioniaLoginPage = '/cake_pay_login_page';
|
||||
static const ioniaManageCardsPage = '/manage_cards_page';
|
||||
static const ioniaBuyGiftCardPage = '/buy_gift_card_page';
|
||||
static const ioniaBuyGiftCardDetailPage = '/buy_gift_card_detail_page';
|
||||
static const ioniaVerifyIoniaOtpPage = '/cake_pay_verify_otp_page';
|
||||
static const ioniaDebitCardPage = '/debit_card_page';
|
||||
static const ioniaActivateDebitCardPage = '/activate_debit_card_page';
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/yat/yat_popup.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
|
@ -14,19 +14,15 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/router.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
|
||||
class DashboardPage extends BasePage {
|
||||
DashboardPage({
|
||||
|
@ -85,7 +81,7 @@ class DashboardPage extends BasePage {
|
|||
|
||||
final DashboardViewModel walletViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final controller = PageController(initialPage: 0);
|
||||
final controller = PageController(initialPage: 1);
|
||||
|
||||
var pages = <Widget>[];
|
||||
bool _isEffectsInstalled = false;
|
||||
|
@ -221,7 +217,7 @@ class DashboardPage extends BasePage {
|
|||
if (_isEffectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
pages.add(MarketPlacePage());
|
||||
pages.add(balancePage);
|
||||
pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
|
||||
_isEffectsInstalled = true;
|
||||
|
|
52
lib/src/screens/dashboard/widgets/market_place_page.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/market_place_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class MarketPlacePage extends StatelessWidget {
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: RawScrollbar(
|
||||
thumbColor: Colors.white.withOpacity(0.15),
|
||||
radius: Radius.circular(20),
|
||||
isAlwaysShown: true,
|
||||
thickness: 2,
|
||||
controller: _scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
Text(
|
||||
S.of(context).market_place,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: <Widget>[
|
||||
SizedBox(height: 20),
|
||||
MarketPlaceItem(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.ioniaWelcomePage),
|
||||
title: S.of(context).cake_pay_title,
|
||||
subTitle: S.of(context).cake_pay_subtitle,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
143
lib/src/screens/ionia/auth/ionia_create_account_page.dart
Normal file
|
@ -0,0 +1,143 @@
|
|||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
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/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_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)
|
||||
: _emailFocus = FocusNode(),
|
||||
_emailController = TextEditingController(),
|
||||
_formKey = GlobalKey<FormState>() {
|
||||
_emailController.text = _ioniaViewModel.email;
|
||||
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
|
||||
}
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final FocusNode _emailFocus;
|
||||
final TextEditingController _emailController;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.sign_up,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
_onCreateUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
_onCreateSuccessful(context, _ioniaViewModel);
|
||||
}
|
||||
});
|
||||
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
focusNode: _emailFocus,
|
||||
validator: EmailValidator(),
|
||||
controller: _emailController,
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).create_account,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
await _ioniaViewModel.createUser(_emailController.text);
|
||||
},
|
||||
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: S.of(context).agree_to,
|
||||
style: TextStyle(
|
||||
color: Color(0xff7A93BA),
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: S.of(context).settings_terms_and_conditions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' ${S.of(context).and} '),
|
||||
TextSpan(
|
||||
text: S.of(context).privacy_policy,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' ${S.of(context).by_cake_pay}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreateUserFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.create_account,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onCreateSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
arguments: [ioniaViewModel.email, ioniaViewModel],
|
||||
);
|
||||
}
|
112
lib/src/screens/ionia/auth/ionia_login_page.dart
Normal file
|
@ -0,0 +1,112 @@
|
|||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
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/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_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)
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_emailController = TextEditingController() {
|
||||
_emailController.text = _ioniaViewModel.email;
|
||||
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
|
||||
}
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
@override
|
||||
Color get titleColor => Colors.black;
|
||||
|
||||
final TextEditingController _emailController;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.login,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
_onLoginUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
_onLoginSuccessful(context, _ioniaViewModel);
|
||||
}
|
||||
});
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
validator: EmailValidator(),
|
||||
controller: _emailController,
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).login,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
await _ioniaViewModel.createUser(_emailController.text);
|
||||
},
|
||||
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onLoginUserFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.login,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onLoginSuccessful(BuildContext context, IoniaViewModel ioniaViewModel) => Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
arguments: [ioniaViewModel.email, ioniaViewModel],
|
||||
);
|
||||
}
|
132
lib/src/screens/ionia/auth/ionia_verify_otp_page.dart
Normal file
|
@ -0,0 +1,132 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
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/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:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class IoniaVerifyIoniaOtp extends BasePage {
|
||||
|
||||
IoniaVerifyIoniaOtp(this._ioniaViewModel, this._email)
|
||||
: _codeController = TextEditingController(),
|
||||
_codeFocus = FocusNode() {
|
||||
_codeController.addListener(() {
|
||||
final otp = _codeController.text;
|
||||
_ioniaViewModel.otp = otp;
|
||||
if (otp.length > 3) {
|
||||
_ioniaViewModel.otpState = IoniaOtpSendEnabled();
|
||||
} else {
|
||||
_ioniaViewModel.otpState = IoniaOtpSendDisabled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
final String _email;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.verification,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final TextEditingController _codeController;
|
||||
final FocusNode _codeFocus;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _ioniaViewModel.otpState, (IoniaOtpState state) {
|
||||
if (state is IoniaOtpFailure) {
|
||||
_onOtpFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaOtpSuccess) {
|
||||
_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,
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onOtpFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.verification,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onOtpSuccessful(BuildContext context) =>
|
||||
Navigator.pushNamedAndRemoveUntil(context, Routes.ioniaManageCardsPage, ModalRoute.withName(Routes.dashboard));
|
||||
}
|
104
lib/src/screens/ionia/auth/ionia_welcome_page.dart
Normal file
|
@ -0,0 +1,104 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
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/view_model/ionia/ionia_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);
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.welcome_to_cakepay,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) {
|
||||
if (state) {
|
||||
Navigator.pushReplacementNamed(context, Routes.ioniaDebitCardPage);
|
||||
}
|
||||
});
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 100),
|
||||
Text(
|
||||
S.of(context).about_cake_pay,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
S.of(context).cake_pay_account_note,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
PrimaryButton(
|
||||
text: S.of(context).create_account,
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
S.of(context).already_have_account,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.ioniaLoginPage),
|
||||
child: Text(
|
||||
S.of(context).login,
|
||||
style: TextStyle(
|
||||
color: Palette.blueCraiola,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
116
lib/src/screens/ionia/cards/ionia_activate_debit_card_page.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.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/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_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);
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.debit_card,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _ioniaViewModel.createCardState, (IoniaCreateCardState state) {
|
||||
if (state is IoniaCreateCardFailure) {
|
||||
_onCreateCardFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateCardSuccess) {
|
||||
_onCreateCardSuccess(context);
|
||||
}
|
||||
});
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Text(S.of(context).debit_card_terms),
|
||||
SizedBox(height: 24),
|
||||
Text(S.of(context).please_reference_document),
|
||||
SizedBox(height: 40),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextIconButton(
|
||||
label: S.current.cardholder_agreement,
|
||||
onTap: () {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextIconButton(
|
||||
label: S.current.e_sign_consent,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSection: LoadingPrimaryButton(
|
||||
onPressed: () {
|
||||
_ioniaViewModel.createCard();
|
||||
},
|
||||
isLoading: _ioniaViewModel.createCardState is IoniaCreateCardLoading,
|
||||
text: S.of(context).agree_and_continue,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreateCardFailure(BuildContext context, String errorMessage) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.error,
|
||||
alertContent: errorMessage,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _onCreateCardSuccess(BuildContext context) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaDebitCardPage,
|
||||
);
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).congratulations,
|
||||
alertContent: S.of(context).you_now_have_debit_card,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
322
lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart
Normal file
|
@ -0,0 +1,322 @@
|
|||
import 'package:cake_wallet/di.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_with_two_actions.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/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:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaBuyGiftCardDetailPage extends StatelessWidget {
|
||||
ThemeBase get currentTheme => getIt.get<SettingsStore>().currentTheme;
|
||||
|
||||
Color get backgroundLightColor => Colors.white;
|
||||
|
||||
Color get backgroundDarkColor => PaletteDark.backgroundColor;
|
||||
|
||||
void onClose(BuildContext context) => Navigator.of(context).pop();
|
||||
|
||||
Widget leading(BuildContext context) {
|
||||
if (ModalRoute.of(context).isFirst) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
size: 16,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => onClose(context),
|
||||
child: _backButton),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
'AppleBees',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _backgroundColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor;
|
||||
|
||||
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()],
|
||||
),
|
||||
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(
|
||||
'\$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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Divider(),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
S.of(context).you_pay,
|
||||
style: textSmall(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'22.3435345000 XMR',
|
||||
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()
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(S.of(context).settings_terms_and_conditions,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.body1.color,
|
||||
).copyWith(fontSize: 12)),
|
||||
SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void purchaseCard(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return IoniaConfirmModal(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
alertContent: SizedBox(
|
||||
//Todo:: substitute this widget with modal content
|
||||
height: 200,
|
||||
),
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TipButtonGroup extends StatefulWidget {
|
||||
const TipButtonGroup({
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TipButtonGroupState createState() => _TipButtonGroupState();
|
||||
}
|
||||
|
||||
class _TipButtonGroupState extends State<TipButtonGroup> {
|
||||
String selectedTip;
|
||||
bool _isSelected(String value) {
|
||||
return selectedTip == 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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
this.isSelected = false,
|
||||
});
|
||||
|
||||
@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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
157
lib/src/screens/ionia/cards/ionia_buy_gift_card.dart
Normal file
|
@ -0,0 +1,157 @@
|
|||
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/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:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaBuyGiftCardPage extends BasePage {
|
||||
IoniaBuyGiftCardPage()
|
||||
: _amountFieldFocus = FocusNode(),
|
||||
_amountController = TextEditingController();
|
||||
@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;
|
||||
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),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).min_amount('5'),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
S.of(context).max_amount('20000'),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
onTap: () {},
|
||||
title: 'Applebee’s',
|
||||
hasDiscount: true,
|
||||
subTitle: 'subTitle',
|
||||
logoUrl: '',
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardDetailPage),
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
382
lib/src/screens/ionia/cards/ionia_debit_card_page.dart
Normal file
|
@ -0,0 +1,382 @@
|
|||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.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/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:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaDebitCardPage extends BasePage {
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
IoniaDebitCardPage(this._ioniaViewModel);
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.debit_card,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final cardState = _ioniaViewModel.cardState;
|
||||
if (cardState is IoniaFetchingCard) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (cardState is IoniaCardSuccess) {
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _IoniaDebitCard(
|
||||
cardInfo: cardState.card,
|
||||
),
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Text(
|
||||
S.of(context).billing_address_info,
|
||||
style: textSmall(color: Theme.of(context).textTheme.display1.color),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
PrimaryButton(
|
||||
text: S.of(context).order_physical_card,
|
||||
onPressed: () {},
|
||||
color: Color(0xffE9F2FC),
|
||||
textColor: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
PrimaryButton(
|
||||
text: S.of(context).add_value,
|
||||
onPressed: () {},
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_IoniaDebitCard(isCardSample: true),
|
||||
SizedBox(height: 40),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextIconButton(
|
||||
label: S.current.how_to_use_card,
|
||||
onTap: () => _showHowToUseCard(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextIconButton(
|
||||
label: S.current.frequently_asked_questions,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
margin: EdgeInsets.all(8),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromRGBO(233, 242, 252, 1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: S.of(context).get_a,
|
||||
style: textMedium(color: Theme.of(context).textTheme.display2.color),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: S.of(context).digital_and_physical_card,
|
||||
style: textMediumBold(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
TextSpan(
|
||||
text: S.of(context).get_card_note,
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 32,
|
||||
),
|
||||
bottomSection: PrimaryButton(
|
||||
text: S.of(context).activate,
|
||||
onPressed: () => _showHowToUseCard(context, activate: true),
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showHowToUseCard(BuildContext context, {bool activate = false}) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
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(
|
||||
S.of(context).signup_for_card_accept_terms,
|
||||
style: textSmallSemiBold(
|
||||
color: Theme.of(context).textTheme.display2.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
_TitleSubtitleTile(
|
||||
title: S.of(context).add_fund_to_card('1000'),
|
||||
subtitle: S.of(context).use_card_info_two,
|
||||
),
|
||||
SizedBox(height: 21),
|
||||
_TitleSubtitleTile(
|
||||
title: S.of(context).use_card_info_three,
|
||||
subtitle: S.of(context).optionally_order_card,
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
PrimaryButton(
|
||||
onPressed: () => activate
|
||||
? Navigator.pushNamed(context, Routes.ioniaActivateDebitCardPage)
|
||||
: 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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _IoniaDebitCard extends StatefulWidget {
|
||||
final bool isCardSample;
|
||||
final IoniaVirtualCard cardInfo;
|
||||
const _IoniaDebitCard({
|
||||
Key key,
|
||||
this.isCardSample = false,
|
||||
this.cardInfo,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_IoniaDebitCardState createState() => _IoniaDebitCardState();
|
||||
}
|
||||
|
||||
class _IoniaDebitCardState extends State<_IoniaDebitCard> {
|
||||
bool _showDetails = false;
|
||||
void _toggleVisibility() {
|
||||
setState(() => _showDetails = !_showDetails);
|
||||
}
|
||||
|
||||
String _formatPan(String pan) {
|
||||
if (pan == null) return '';
|
||||
return pan.replaceAllMapped(RegExp(r'.{4}'), (match) => '${match.group(0)} ');
|
||||
}
|
||||
|
||||
String get _getLast4 => widget.isCardSample ? '0000' : widget.cardInfo.pan.substring(widget.cardInfo.pan.length - 5);
|
||||
|
||||
String get _getSpendLimit => widget.isCardSample ? '10000' : widget.cardInfo.spendLimit.toStringAsFixed(2);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 19),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.current.cakepay_prepaid_card,
|
||||
style: textSmall(),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/mastercard.png',
|
||||
width: 54,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget.isCardSample ? S.of(context).upto(_getSpendLimit) : '\$$_getSpendLimit',
|
||||
style: textXLargeSemiBold(),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
_showDetails ? _formatPan(widget.cardInfo.pan) : '**** **** **** $_getLast4',
|
||||
style: textMediumSemiBold(),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.isCardSample)
|
||||
Text(
|
||||
S.current.no_id_needed,
|
||||
style: textMediumBold(),
|
||||
)
|
||||
else ...[
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'CVV',
|
||||
style: textXSmallSemiBold(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
_showDetails ? widget.cardInfo.cvv : '***',
|
||||
style: textMediumSemiBold(),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).expires,
|
||||
style: textXSmallSemiBold(),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${widget.cardInfo.expirationMonth ?? S.of(context).mm}/${widget.cardInfo.expirationYear ?? S.of(context).yy}',
|
||||
style: textMediumSemiBold(),
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
if (!widget.isCardSample) ...[
|
||||
SizedBox(height: 8),
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () => _toggleVisibility(),
|
||||
child: Text(
|
||||
_showDetails ? S.of(context).hide_details : S.of(context).show_details,
|
||||
style: textSmall(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleSubtitleTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
const _TitleSubtitleTile({
|
||||
Key key,
|
||||
@required this.title,
|
||||
@required this.subtitle,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textSmallSemiBold(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textSmall(color: Theme.of(context).textTheme.display2.color),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
247
lib/src/screens/ionia/cards/ionia_manage_cards_page.dart
Normal file
|
@ -0,0 +1,247 @@
|
|||
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/widgets/market_place_item.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaManageCardsPage extends BasePage {
|
||||
IoniaManageCardsPage(this._ioniaViewModel);
|
||||
|
||||
final IoniaViewModel _ioniaViewModel;
|
||||
|
||||
@override
|
||||
Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
|
||||
|
||||
@override
|
||||
Color get backgroundDarkColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
Color get titleColor => currentTheme.type == ThemeType.bright ? Colors.white : Colors.black;
|
||||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).accentColor,
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor,
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
),
|
||||
child: scaffold,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
Widget get endDrawer => CardMenu();
|
||||
|
||||
@override
|
||||
Widget leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: titleColor ?? Theme.of(context).primaryTextTheme.title.color,
|
||||
size: 16,
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Navigator.pushReplacementNamed(context, Routes.dashboard),
|
||||
child: _backButton),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).manage_cards,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_TrailingIcon(
|
||||
asset: 'assets/images/card.png',
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.ioniaDebitCardPage),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
_TrailingIcon(
|
||||
asset: 'assets/images/profile.png',
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final filterIcon = Image.asset(
|
||||
'assets/images/filter.png',
|
||||
color: Theme.of(context).textTheme.caption.decorationColor,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarketPlaceItem(
|
||||
onTap: () {},
|
||||
title: S.of(context).setup_your_debit_card,
|
||||
subTitle: S.of(context).no_id_required,
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
height: 32,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _SearchWidget()),
|
||||
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: RawScrollbar(
|
||||
thumbColor: Colors.white.withOpacity(0.15),
|
||||
radius: Radius.circular(20),
|
||||
isAlwaysShown: true,
|
||||
thickness: 2,
|
||||
controller: _scrollController,
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
controller: _scrollController,
|
||||
itemCount: 20,
|
||||
separatorBuilder: (_, __) => SizedBox(height: 4),
|
||||
itemBuilder: (_, index) {
|
||||
return CardItem(
|
||||
logoUrl: '',
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage),
|
||||
title: 'Amazon',
|
||||
subTitle: 'Onlin',
|
||||
hasDiscount: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchWidget extends StatelessWidget {
|
||||
const _SearchWidget({
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Image.asset(
|
||||
'assets/images/search_icon.png',
|
||||
color: Theme.of(context).textTheme.caption.decorationColor,
|
||||
),
|
||||
);
|
||||
|
||||
return TextField(
|
||||
style: TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 10,
|
||||
),
|
||||
fillColor: Colors.white.withOpacity(0.15),
|
||||
hintText: 'Search',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
),
|
||||
alignLabelWithHint: true,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
suffixIcon: searchIcon,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.white.withOpacity(0.2)),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrailingIcon extends StatelessWidget {
|
||||
final String asset;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _TrailingIcon({this.asset, this.onPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.centerRight,
|
||||
width: 25,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: onPressed,
|
||||
child: Image.asset(
|
||||
asset,
|
||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
9
lib/src/screens/ionia/ionia.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
export 'auth/ionia_welcome_page.dart';
|
||||
export 'auth/ionia_create_account_page.dart';
|
||||
export 'auth/ionia_login_page.dart';
|
||||
export 'auth/ionia_verify_otp_page.dart';
|
||||
export 'cards/ionia_activate_debit_card_page.dart';
|
||||
export 'cards/ionia_buy_card_detail_page.dart';
|
||||
export 'cards/ionia_manage_cards_page.dart';
|
||||
export 'cards/ionia_debit_card_page.dart';
|
||||
export 'cards/ionia_buy_gift_card.dart';
|
122
lib/src/screens/ionia/widgets/card_item.dart
Normal file
|
@ -0,0 +1,122 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/widgets/discount_badge.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardItem extends StatelessWidget {
|
||||
|
||||
CardItem({
|
||||
@required this.onTap,
|
||||
@required this.title,
|
||||
@required this.subTitle,
|
||||
this.logoUrl,
|
||||
this.hasDiscount = false,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final String logoUrl;
|
||||
final bool hasDiscount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.20),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (logoUrl != null) ...[
|
||||
ClipOval(
|
||||
child: Image.network(
|
||||
logoUrl,
|
||||
width: 42.0,
|
||||
height: 42.0,
|
||||
loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) {
|
||||
if (loadingProgress == null) {
|
||||
return child;
|
||||
} else {
|
||||
return _PlaceholderContainer(text: 'Logo');
|
||||
}
|
||||
},
|
||||
errorBuilder: (_, __, ___) => _PlaceholderContainer(text: '!'),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
],
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Palette.stateGray,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Palette.niagara ,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (hasDiscount)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: DiscountBadge(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlaceholderContainer extends StatelessWidget {
|
||||
|
||||
const _PlaceholderContainer({@required this.text});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
11
lib/src/screens/ionia/widgets/card_menu.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
146
lib/src/screens/ionia/widgets/confirm_modal.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IoniaConfirmModal extends StatelessWidget {
|
||||
IoniaConfirmModal({
|
||||
@required this.alertTitle,
|
||||
@required this.alertContent,
|
||||
@required this.leftButtonText,
|
||||
@required this.rightButtonText,
|
||||
@required this.actionLeftButton,
|
||||
@required this.actionRightButton,
|
||||
this.leftActionColor,
|
||||
this.rightActionColor,
|
||||
});
|
||||
|
||||
final String alertTitle;
|
||||
final Widget alertContent;
|
||||
final String leftButtonText;
|
||||
final String rightButtonText;
|
||||
final VoidCallback actionLeftButton;
|
||||
final VoidCallback actionRightButton;
|
||||
final Color leftActionColor;
|
||||
final Color rightActionColor;
|
||||
|
||||
Widget actionButtons(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
IoniaActionButton(
|
||||
buttonText: leftButtonText,
|
||||
action: actionLeftButton,
|
||||
backgoundColor: leftActionColor,
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 52,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
IoniaActionButton(
|
||||
buttonText: rightButtonText,
|
||||
action: actionRightButton,
|
||||
backgoundColor: rightActionColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget title(BuildContext context) {
|
||||
return Text(
|
||||
alertTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => null,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
width: 327,
|
||||
color: Theme.of(context).accentTextTheme.title.decorationColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
|
||||
child: title(context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
alertContent,
|
||||
actionButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IoniaActionButton extends StatelessWidget {
|
||||
const IoniaActionButton({
|
||||
@required this.buttonText,
|
||||
@required this.action,
|
||||
this.backgoundColor,
|
||||
});
|
||||
|
||||
final String buttonText;
|
||||
final VoidCallback action;
|
||||
final Color backgoundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: backgoundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: action,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
buttonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: backgoundColor != null ? Colors.white : Theme.of(context).primaryTextTheme.body1.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
35
lib/src/screens/ionia/widgets/text_icon_button.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextIconButton extends StatelessWidget {
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
const TextIconButton({
|
||||
Key key,
|
||||
this.label,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
@required this.rightButtonText,
|
||||
@required this.actionLeftButton,
|
||||
@required this.actionRightButton,
|
||||
this.alertBarrierDismissible = true
|
||||
this.alertBarrierDismissible = true,
|
||||
this.isDividerExist = false,
|
||||
this.leftActionColor,
|
||||
this.rightActionColor,
|
||||
});
|
||||
|
||||
final String alertTitle;
|
||||
|
@ -20,6 +23,9 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
final VoidCallback actionLeftButton;
|
||||
final VoidCallback actionRightButton;
|
||||
final bool alertBarrierDismissible;
|
||||
final Color leftActionColor;
|
||||
final Color rightActionColor;
|
||||
final bool isDividerExist;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
|
@ -35,4 +41,10 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
|||
VoidCallback get actionRight => actionRightButton;
|
||||
@override
|
||||
bool get barrierDismissible => alertBarrierDismissible;
|
||||
}
|
||||
@override
|
||||
Color get leftButtonColor => leftActionColor;
|
||||
@override
|
||||
Color get rightButtonColor => rightActionColor;
|
||||
@override
|
||||
bool get isDividerExists => isDividerExist;
|
||||
}
|
||||
|
|
|
@ -46,30 +46,28 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body2.decorationColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionLeft,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
leftActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body2
|
||||
.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
),
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body2.decorationColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionLeft,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
leftActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body2.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)),
|
||||
Container(
|
||||
width: 1,
|
||||
height: 52,
|
||||
|
@ -77,30 +75,28 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body1.backgroundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionRight,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
rightActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body1
|
||||
.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
),
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
color: Theme.of(context).accentTextTheme.body1.backgroundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: FlatButton(
|
||||
onPressed: actionRight,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Text(
|
||||
rightActionButtonText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.body1.backgroundColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -108,9 +104,7 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => barrierDismissible
|
||||
? Navigator.of(context).pop()
|
||||
: null,
|
||||
onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: BackdropFilter(
|
||||
|
@ -136,14 +130,14 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
child: title(context),
|
||||
),
|
||||
isDividerExists
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||
child: content(context),
|
||||
|
@ -166,4 +160,4 @@ class BaseAlertDialog extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
31
lib/src/widgets/discount_badge.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
|
||||
class DiscountBadge extends StatelessWidget {
|
||||
const DiscountBadge({
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
Image.asset('assets/images/badge_discount.png'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
S.of(context).discount('20'),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
66
lib/src/widgets/market_place_item.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class MarketPlaceItem extends StatelessWidget {
|
||||
|
||||
|
||||
MarketPlaceItem({
|
||||
@required this.onTap,
|
||||
@required this.title,
|
||||
@required this.subTitle,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).textTheme.title.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.20),
|
||||
),
|
||||
),
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display3
|
||||
.backgroundColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display3
|
||||
.backgroundColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
59
lib/typography.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
const latoFont = "Lato";
|
||||
|
||||
TextStyle textXxSmall({Color color}) => _cakeRegular(10, color);
|
||||
|
||||
TextStyle textXxSmallSemiBold({Color color}) => _cakeSemiBold(10, color);
|
||||
|
||||
TextStyle textXSmall({Color color}) => _cakeRegular(12, color);
|
||||
|
||||
TextStyle textXSmallSemiBold({Color color}) => _cakeSemiBold(12, color);
|
||||
|
||||
TextStyle textSmall({Color color}) => _cakeRegular(14, color);
|
||||
|
||||
TextStyle textSmallSemiBold({Color color}) => _cakeSemiBold(14, color);
|
||||
|
||||
TextStyle textMedium({Color color}) => _cakeRegular(16, color);
|
||||
|
||||
TextStyle textMediumBold({Color color}) => _cakeBold(16, color);
|
||||
|
||||
TextStyle textMediumSemiBold({Color color}) => _cakeSemiBold(22, color);
|
||||
|
||||
TextStyle textLarge({Color color}) => _cakeRegular(18, color);
|
||||
|
||||
TextStyle textLargeSemiBold({Color color}) => _cakeSemiBold(24, color);
|
||||
|
||||
TextStyle textXLarge({Color color}) => _cakeRegular(32, color);
|
||||
|
||||
TextStyle textXLargeSemiBold({Color color}) => _cakeSemiBold(32, color);
|
||||
|
||||
TextStyle _cakeRegular(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _cakeBold(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _cakeSemiBold(double size, Color color) => _textStyle(
|
||||
size: size,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
);
|
||||
|
||||
TextStyle _textStyle({
|
||||
@required double size,
|
||||
@required FontWeight fontWeight,
|
||||
Color color,
|
||||
}) =>
|
||||
TextStyle(
|
||||
fontFamily: latoFont,
|
||||
fontSize: size,
|
||||
fontWeight: fontWeight,
|
||||
color: color ?? Colors.white,
|
||||
);
|
91
lib/view_model/ionia/ionia_view_model.dart
Normal file
|
@ -0,0 +1,91 @@
|
|||
import 'package:cake_wallet/ionia/ionia.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_virtual_card.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
part 'ionia_view_model.g.dart';
|
||||
|
||||
class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel;
|
||||
|
||||
abstract class IoniaViewModelBase with Store {
|
||||
IoniaViewModelBase({this.ioniaService})
|
||||
: createUserState = IoniaCreateStateSuccess(),
|
||||
otpState = IoniaOtpSendDisabled(),
|
||||
cardState = IoniaNoCardState() {
|
||||
_getCard();
|
||||
_getAuthStatus().then((value) => isLoggedIn = value);
|
||||
}
|
||||
|
||||
final IoniaService ioniaService;
|
||||
|
||||
@observable
|
||||
IoniaCreateAccountState createUserState;
|
||||
|
||||
@observable
|
||||
IoniaOtpState otpState;
|
||||
|
||||
@observable
|
||||
IoniaCreateCardState createCardState;
|
||||
|
||||
@observable
|
||||
IoniaFetchCardState cardState;
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<IoniaVirtualCard> createCard() async {
|
||||
createCardState = IoniaCreateCardLoading();
|
||||
try {
|
||||
final card = await ioniaService.createCard();
|
||||
createCardState = IoniaCreateCardSuccess();
|
||||
return card;
|
||||
} on Exception catch (e) {
|
||||
createCardState = IoniaCreateCardFailure(error: e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _getCard() async {
|
||||
cardState = IoniaFetchingCard();
|
||||
try {
|
||||
final card = await ioniaService.getCard();
|
||||
|
||||
cardState = IoniaCardSuccess(card: card);
|
||||
} catch (_) {
|
||||
cardState = IoniaFetchCardFailure();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -530,5 +530,69 @@
|
|||
"learn_more" : "Learn More",
|
||||
"search": "Search",
|
||||
"new_template" : "New Template",
|
||||
"electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work"
|
||||
"electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
|
||||
"market_place": "Market place",
|
||||
"cake_pay_title": "Gift cards and debit cards",
|
||||
"cake_pay_subtitle": "Buy gift cards and top up no-KYC debit cards",
|
||||
"about_cake_pay": "CakePay allows you to easily buy gift cards and load up prepaid debit cards with cryptocurrencies, spendable at millions of merchants in the United States.",
|
||||
"cake_pay_account_note": "Make an account to see the available cards. Some are even available at a discount!",
|
||||
"already_have_account": "Already have an account?",
|
||||
"create_account": "Create Account",
|
||||
"privacy_policy": "Privacy policy",
|
||||
"welcome_to_cakepay": "Welcome to CakePay!",
|
||||
"sign_up": "Sign Up",
|
||||
"forgot_password": "Forgot Password",
|
||||
"reset_password": "Reset Password",
|
||||
"manage_cards": "Manage Cards",
|
||||
"setup_your_debit_card": "Set up your debit card",
|
||||
"no_id_required": "No ID required. Top up and spend anywhere",
|
||||
"how_to_use_card": "How to use this card",
|
||||
"purchase_gift_card": "Purchase Gift Card",
|
||||
"verification": "Verification",
|
||||
"fill_code": "Please fill in the verification code provided to your email",
|
||||
"dont_get_code": "Don't get code",
|
||||
"resend_code": "Please resend it",
|
||||
"debit_card": "Debit Card",
|
||||
"cakepay_prepaid_card": "CakePay Prepaid Debit Card",
|
||||
"no_id_needed": "No ID needed!",
|
||||
"frequently_asked_questions": "Frequently asked questions",
|
||||
"debit_card_terms": "The storage and usage of your payment card number (and credentials corresponding to your payment card number) in this digital wallet are subject to the Terms and Conditions of the applicable cardholder agreement with the payment card issuer, as in effect from time to time.",
|
||||
"please_reference_document": "Please reference the documents below for more information.",
|
||||
"cardholder_agreement": "Cardholder Agreement",
|
||||
"e_sign_consent": "E-Sign Consent",
|
||||
"agree_and_continue": "Agree & Continue",
|
||||
"email_address": "Email Address",
|
||||
"agree_to": "By creating account you agree to the ",
|
||||
"and": "and",
|
||||
"enter_code": "Enter code",
|
||||
"congratulations": "Congratulations!",
|
||||
"you_now_have_debit_card": "You now have a debit card",
|
||||
"min_amount" : "Min: ${value}",
|
||||
"max_amount" : "Max: ${value}",
|
||||
"enter_amount": "Enter Amount",
|
||||
"billing_address_info": "If asked for a billing address, provide your shipping address",
|
||||
"order_physical_card": "Order Physical Card",
|
||||
"add_value": "Add value",
|
||||
"activate": "Activate",
|
||||
"get_a": "Get a ",
|
||||
"digital_and_physical_card": " digital and physical prepaid debit card",
|
||||
"get_card_note": " that you can reload with digital currencies. No additional information needed!",
|
||||
"signup_for_card_accept_terms": "Sign up for the card and accept the terms.",
|
||||
"add_fund_to_card": "Add prepaid funds to the cards (up to ${value})",
|
||||
"use_card_info_two": "Funds are converted to USD when the held in the prepaid account, not in digital currencies.",
|
||||
"use_card_info_three": "Use the digital card online or with contactless payment methods.",
|
||||
"optionally_order_card": "Optionally order a physical card.",
|
||||
"hide_details" : "Hide Details",
|
||||
"show_details" : "Show Details",
|
||||
"upto": "up to ${value}",
|
||||
"discount": "Save ${value}%",
|
||||
"gift_card_amount": "Gift Card Amount",
|
||||
"bill_amount": "Bill amount",
|
||||
"you_pay": "You pay",
|
||||
"tip": "Tip:",
|
||||
"custom": "custom",
|
||||
"by_cake_pay": "by CakePay",
|
||||
"expires": "Expires",
|
||||
"mm": "MM",
|
||||
"yy": "YY"
|
||||
}
|
||||
|
|