* Initial ionia service

* 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

* Add ionia merchant sevic

* Add anypay. Add purschase gift card.

* display virtual card (#385)

* display virtual card

* fix formatting

* Remove IoniaMerchantService from IoniaViewModel

* Add hex and txKey for monero pending transaction.

* Changed monero version and monero repo to cake tech.

* Add anypay payment. Add filter by search for ionia, add get purchased items for ionia.

* Fix for get transactions for hidden addresses for electrum wallet

* Add ionia categories.

* Add anypay commited info for payments.

* Update UI with new fixes (#400)

* Change ionia base url. Add exception throwing for error messaging for some of ionia calls.

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

* refactor tips (#406)

* refactor tips

* refactor ionia tips implementation

* Cw 115 implement gift cards list for ionia (#405)

* Implement show purchased cards

* fix padding

* Fixes for getting of purchased gift cards.

* Implement gift card details screen (#408)

* Implement gift card details screen

* Add redeem for ionia gift cards

* Fix navigation after ionia opt redirection.

* Fix update gift cards list.

* Add payment status update for ionia.

* Add usage instruction to gift card.

* Add copy for ionia gift card info.

* Change version for Cake Wallet ios.

* Add localisation (#414)

* Fixes for fiat amounts for ionia.

* CW-128 marketplace screen text changes (#416)

* Change text on marketplace

* fix build issues

* fix build

* UI fixes for ionia.

* UI fixes for ionia. (#421)

* CW-129 ionia welcome screen text changes (#418)

* update welcome text

* Update localization

* Cw 133 (#422)

* UI fixes for ionia.

* Fixes for display card item on gift cards screen.

* Fix signup page (#419)

* Changed tips for ionia.

* Cw 132 (#425)

* UI fixes for ionia.

* Changed tips for ionia.

* Cw 131 (#426)

* UI fixes for ionia.

* Changed tips for ionia.

* Fixes for IoniaBuyGiftCardDetailPage screen. Renamed 'Manage Cards' to 'Gift Cards'. Hide discount badge label for 0 discount.

* Change ionia heading font style (#427)

* Fix for AddressResolver in di

* Changed build number for Cake Wallet ios.

* fix currency format for card details and routing for mark as redeemed (#431)

* fix terms and condition overflow in ionia (#430)

* fix terms and condition scroll

* fix color issues

* reuse

* refactor  widget

* Remove IoniaTokenService

* Change api for ionia to staging

* Update versions for Cake Wallet for android and ios.

* Fixes for instructions. Remove diplay error on payment status screen.

* Change build versions for Cake Wallet

* Add ionia sign in.

* Update for discounts and statuses for ionia merch.

* Fixes for qr/barcode on ionia gift card screen.

* Fixed formatting for display ionia discounts.

* Fix merchant.discount.toStringAsFixed issue

* Add savingsPercentage to ionia merch discount.

* Change build number for Cake Wallet ios and android.

* Disable ionia for haven (#440)

Co-authored-by: Godwin Asuquo <41484542+godilite@users.noreply.github.com>
This commit is contained in:
mkyq
2022-07-28 18:03:16 +01:00
committed by GitHub
co-authored by Godwin Asuquo
parent b72443a8c4
commit 418c9563fe
115 changed files with 7726 additions and 184 deletions
@@ -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(dashboardViewModel: walletViewModel));
pages.add(balancePage);
pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
_isEffectsInstalled = true;
@@ -0,0 +1,80 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/market_place_item.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
class MarketPlacePage extends StatelessWidget {
MarketPlacePage({@required this.dashboardViewModel});
final DashboardViewModel dashboardViewModel;
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: () =>_navigatorToGiftCardsPage(context),
title: S.of(context).cake_pay_title,
subTitle: S.of(context).cake_pay_subtitle,
),
],
),
),
],
),
),
),
);
}
void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type;
switch (walletType) {
case WalletType.haven:
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).gift_cards_unavailable,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
break;
default:
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
}
}
}
@@ -0,0 +1,154 @@
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/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/gestures.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';
import 'package:url_launcher/url_launcher.dart';
class IoniaCreateAccountPage extends BasePage {
IoniaCreateAccountPage(this._authViewModel)
: _emailFocus = FocusNode(),
_emailController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_emailController.text = _authViewModel.email;
_emailController.addListener(() => _authViewModel.email = _emailController.text);
}
final IoniaAuthViewModel _authViewModel;
final GlobalKey<FormState> _formKey;
final FocusNode _emailFocus;
final TextEditingController _emailController;
static const privacyPolicyUrl = 'https://ionia.docsend.com/view/jaqsmbq9w7dzvnqf';
static const termsAndConditionsUrl = 'https://ionia.docsend.com/view/hi9awnwxr6mqgiqj';
@override
Widget middle(BuildContext context) {
return Text(
S.current.sign_up,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
reaction((_) => _authViewModel.createUserState, (IoniaCreateAccountState state) {
if (state is IoniaCreateStateFailure) {
_onCreateUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onCreateSuccessful(context, _authViewModel);
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: BaseTextFormField(
hintText: S.of(context).email_address,
focusNode: _emailFocus,
validator: EmailValidator(),
keyboardType: TextInputType.emailAddress,
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 _authViewModel.createUser(_emailController.text);
},
isLoading: _authViewModel.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,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
if (await canLaunch(termsAndConditionsUrl)) await launch(termsAndConditionsUrl);
},
),
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,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
if (await canLaunch(privacyPolicyUrl)) await launch(privacyPolicyUrl);
}),
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, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
context,
Routes.ioniaVerifyIoniaOtpPage,
arguments: [authViewModel.email, false],
);
}
@@ -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/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaLoginPage extends BasePage {
IoniaLoginPage(this._authViewModel)
: _formKey = GlobalKey<FormState>(),
_emailController = TextEditingController() {
_emailController.text = _authViewModel.email;
_emailController.addListener(() => _authViewModel.email = _emailController.text);
}
final GlobalKey<FormState> _formKey;
final IoniaAuthViewModel _authViewModel;
@override
Color get titleColor => Colors.black;
final TextEditingController _emailController;
@override
Widget middle(BuildContext context) {
return Text(
S.current.login,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
if (state is IoniaCreateStateFailure) {
_onLoginUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onLoginSuccessful(context, _authViewModel);
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: BaseTextFormField(
hintText: S.of(context).email_address,
keyboardType: TextInputType.emailAddress,
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 _authViewModel.signIn(_emailController.text);
},
isLoading: _authViewModel.signInState 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, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
context,
Routes.ioniaVerifyIoniaOtpPage,
arguments: [authViewModel.email, true],
);
}
@@ -0,0 +1,151 @@
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/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
class IoniaVerifyIoniaOtp extends BasePage {
IoniaVerifyIoniaOtp(this._authViewModel, this._email, this.isSignIn)
: _codeController = TextEditingController(),
_codeFocus = FocusNode() {
_codeController.addListener(() {
final otp = _codeController.text;
_authViewModel.otp = otp;
if (otp.length > 3) {
_authViewModel.otpState = IoniaOtpSendEnabled();
} else {
_authViewModel.otpState = IoniaOtpSendDisabled();
}
});
}
final IoniaAuthViewModel _authViewModel;
final bool isSignIn;
final String _email;
@override
Widget middle(BuildContext context) {
return Text(
S.current.verification,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
final TextEditingController _codeController;
final FocusNode _codeFocus;
@override
Widget body(BuildContext context) {
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
if (state is IoniaOtpFailure) {
_onOtpFailure(context, state.error);
}
if (state is IoniaOtpSuccess) {
_onOtpSuccessful(context);
}
});
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _codeFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
height: 0,
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
BaseTextFormField(
hintText: S.of(context).enter_code,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
focusNode: _codeFocus,
controller: _codeController,
),
SizedBox(height: 14),
Text(
S.of(context).fill_code,
style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
),
SizedBox(height: 34),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(S.of(context).dont_get_code),
SizedBox(width: 20),
InkWell(
onTap: () => isSignIn
? _authViewModel.signIn(_email)
: _authViewModel.createUser(_email),
child: Text(
S.of(context).resend_code,
style: textSmallSemiBold(color: Palette.blueCraiola),
),
),
],
),
],
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
bottomSection: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).continue_text,
onPressed: () async => await _authViewModel.verifyEmail(_codeController.text),
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
isLoading: _authViewModel.otpState is IoniaOtpValidating,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(height: 20),
],
),
],
),
),
),
);
}
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.of(context)
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
}
@@ -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/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class IoniaWelcomePage extends BasePage {
IoniaWelcomePage(this._cardsListViewModel);
@override
Widget middle(BuildContext context) {
return Text(
S.current.welcome_to_cakepay,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
final IoniaGiftCardsListViewModel _cardsListViewModel;
@override
Widget body(BuildContext context) {
reaction((_) => _cardsListViewModel.isLoggedIn, (bool state) {
if (state) {
Navigator.pushReplacementNamed(context, Routes.ioniaManageCardsPage);
}
});
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: 18,
letterSpacing: 1.5,
fontWeight: FontWeight.w900,
),
),
),
SizedBox(height: 20)
],
)
],
),
);
}
}
@@ -0,0 +1,181 @@
import 'dart:ffi';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaAccountCardsPage extends BasePage {
IoniaAccountCardsPage(this.ioniaAccountViewModel);
final IoniaAccountViewModel ioniaAccountViewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.of(context).cards,
style: textLargeSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
return _IoniaCardTabs(ioniaAccountViewModel);
}
}
class _IoniaCardTabs extends StatefulWidget {
_IoniaCardTabs(this.ioniaAccountViewModel);
final IoniaAccountViewModel ioniaAccountViewModel;
@override
_IoniaCardTabsState createState() => _IoniaCardTabsState();
}
class _IoniaCardTabsState extends State<_IoniaCardTabs> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
@override
void dispose() {
super.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 45,
width: 230,
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(
25.0,
),
),
child: Theme(
data: ThemeData(primaryTextTheme: TextTheme(body2: TextStyle(backgroundColor: Colors.transparent))),
child: TabBar(
controller: _tabController,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25.0,
),
color: Theme.of(context).accentTextTheme.body2.color,
),
labelColor: Theme.of(context).primaryTextTheme.display4.backgroundColor,
unselectedLabelColor: Theme.of(context).primaryTextTheme.title.color,
tabs: [
Tab(
text: S.of(context).active,
),
Tab(
text: S.of(context).redeemed,
),
],
),
),
),
SizedBox(height: 16),
Expanded(
child: Observer(builder: (_) {
final viewModel = widget.ioniaAccountViewModel;
return TabBarView(
controller: _tabController,
children: [
_IoniaCardListView(
emptyText: S.of(context).gift_card_balance_note,
merchList: viewModel.activeMechs,
onTap: (giftCard) {
Navigator.pushNamed(
context,
Routes.ioniaGiftCardDetailPage,
arguments: [giftCard])
.then((_) => viewModel.updateUserGiftCards());
}),
_IoniaCardListView(
emptyText: S.of(context).gift_card_redeemed_note,
merchList: viewModel.redeemedMerchs,
onTap: (giftCard) {
Navigator.pushNamed(
context,
Routes.ioniaGiftCardDetailPage,
arguments: [giftCard])
.then((_) => viewModel.updateUserGiftCards());
}),
],
);
}),
),
],
),
);
}
}
class _IoniaCardListView extends StatelessWidget {
_IoniaCardListView({
Key key,
@required this.emptyText,
@required this.merchList,
@required this.onTap,
}) : super(key: key);
final String emptyText;
final List<IoniaGiftCard> merchList;
final void Function(IoniaGiftCard giftCard) onTap;
@override
Widget build(BuildContext context) {
return merchList.isEmpty
? Center(
child: Text(
emptyText,
textAlign: TextAlign.center,
style: textSmall(
color: Theme.of(context).primaryTextTheme.overline.color,
),
),
)
: ListView.builder(
itemCount: merchList.length,
itemBuilder: (context, index) {
final merchant = merchList[index];
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: CardItem(
onTap: () => onTap?.call(merchant),
title: merchant.legalName,
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
discount: 0,
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
subtitleColor: Theme.of(context).hintColor,
subTitle: '',
logoUrl: merchant.logoUrl,
),
);
},
);
}
}
@@ -0,0 +1,181 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaAccountPage extends BasePage {
IoniaAccountPage(this.ioniaAccountViewModel);
final IoniaAccountViewModel ioniaAccountViewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.current.account,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
_GradiantContainer(
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Observer(
builder: (_) => RichText(
text: TextSpan(
text: '${ioniaAccountViewModel.countOfMerch}',
style: textLargeSemiBold(),
children: [
TextSpan(
text: ' ${S.of(context).active_cards}',
style: textSmall(color: Colors.white.withOpacity(0.7))),
],
),
)),
InkWell(
onTap: () {
Navigator.pushNamed(context, Routes.ioniaAccountCardsPage)
.then((_) => ioniaAccountViewModel.updateUserGiftCards());
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
S.of(context).view_all,
style: textSmallSemiBold(),
),
),
)
],
),
),
SizedBox(height: 8),
//Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).total_saving,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '\$100',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).last_30_days,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '\$100',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// _GradiantContainer(
// padding: EdgeInsets.all(16),
// width: deviceWidth * 0.28,
// content: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// S.of(context).avg_savings,
// style: textSmall(),
// ),
// SizedBox(height: 8),
// Text(
// '10%',
// style: textMediumSemiBold(),
// ),
// ],
// ),
// ),
// ],
//),
SizedBox(height: 40),
Observer(
builder: (_) => IoniaTile(title: S.of(context).email_address, subTitle: ioniaAccountViewModel.email),
),
Divider()
],
),
bottomSectionPadding: EdgeInsets.all(30),
bottomSection: Column(
children: [
PrimaryButton(
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
text: S.of(context).logout,
onPressed: () {
ioniaAccountViewModel.logout();
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
},
),
],
),
);
}
}
class _GradiantContainer extends StatelessWidget {
const _GradiantContainer({
Key key,
@required this.content,
this.padding,
this.width,
}) : super(key: key);
final Widget content;
final EdgeInsets padding;
final double width;
@override
Widget build(BuildContext context) {
return Container(
child: content,
width: width,
padding: padding ?? EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).accentColor,
],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
);
}
}
@@ -0,0 +1,114 @@
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/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class IoniaActivateDebitCardPage extends BasePage {
IoniaActivateDebitCardPage(this._cardsListViewModel);
final IoniaGiftCardsListViewModel _cardsListViewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.current.debit_card,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
reaction((_) => _cardsListViewModel.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: () {
_cardsListViewModel.createCard();
},
isLoading: _cardsListViewModel.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(),
);
},
);
}
}
@@ -0,0 +1,492 @@
import 'dart:ui';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/confirm_modal.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_alert_model.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/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/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class IoniaBuyGiftCardDetailPage extends BasePage {
IoniaBuyGiftCardDetailPage(this.ioniaPurchaseViewModel);
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
@override
Widget middle(BuildContext context) {
return Text(
ioniaPurchaseViewModel.ioniaMerchant.legalName,
style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
);
}
@override
Widget trailing(BuildContext context)
=> ioniaPurchaseViewModel.ioniaMerchant.discount > 0
? DiscountBadge(percentage: ioniaPurchaseViewModel.ioniaMerchant.discount)
: null;
@override
Widget body(BuildContext context) {
reaction((_) => ioniaPurchaseViewModel.invoiceCreationState, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
reaction((_) => ioniaPurchaseViewModel.invoiceCommittingState, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(
Routes.ioniaPaymentStatusPage,
arguments: [
ioniaPurchaseViewModel.paymentInfo,
ioniaPurchaseViewModel.committedInfo]);
});
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Observer(builder: (_) {
final tipAmount = ioniaPurchaseViewModel.tipAmount;
return Column(
children: [
SizedBox(height: 36),
Container(
padding: EdgeInsets.symmetric(vertical: 24),
margin: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
Text(
S.of(context).gift_card_amount,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$${ioniaPurchaseViewModel.giftCardAmount.toStringAsFixed(2)}',
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(
'\$${ioniaPurchaseViewModel.billAmount.toStringAsFixed(2)}',
style: textLargeSemiBold(),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
S.of(context).tip,
style: textSmall(),
),
SizedBox(height: 4),
Text(
'\$${tipAmount.toStringAsFixed(2)}',
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),
Observer(
builder: (_) => TipButtonGroup(
selectedTip: ioniaPurchaseViewModel.selectedTip.percentage,
tipsList: ioniaPurchaseViewModel.tips,
onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
),
)
],
),
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: TextIconButton(
label: S.of(context).how_to_use_card,
onTap: () => _showHowToUseCard(context, ioniaPurchaseViewModel.ioniaMerchant),
),
),
],
);
}),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: Observer(builder: (_) {
return LoadingPrimaryButton(
isLoading: ioniaPurchaseViewModel.invoiceCreationState is IsExecutingState ||
ioniaPurchaseViewModel.invoiceCommittingState is IsExecutingState,
onPressed: () => purchaseCard(context),
text: S.of(context).purchase_gift_card,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
);
}),
),
SizedBox(height: 8),
InkWell(
onTap: () => _showTermsAndCondition(context),
child: Text(S.of(context).settings_terms_and_conditions,
style: textMediumSemiBold(
color: Theme.of(context).primaryTextTheme.body1.color,
).copyWith(fontSize: 12)),
),
SizedBox(height: 16)
],
),
);
}
void _showTermsAndCondition(BuildContext context) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return IoniaAlertModal(
title: S.of(context).settings_terms_and_conditions,
content: Align(
alignment: Alignment.bottomLeft,
child: Text(
ioniaPurchaseViewModel.ioniaMerchant.termsAndConditions,
style: textMedium(
color: Theme.of(context).textTheme.display2.color,
),
),
),
actionTitle: S.of(context).agree,
showCloseButton: false,
heightFactor: 0.6,
);
});
}
Future<void> purchaseCard(BuildContext context) async {
await ioniaPurchaseViewModel.createInvoice();
if (ioniaPurchaseViewModel.invoiceCreationState is ExecutedSuccessfullyState) {
await _presentSuccessfulInvoiceCreationPopup(context);
}
}
void _showHowToUseCard(
BuildContext context,
IoniaMerchant merchant,
) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return IoniaAlertModal(
title: S.of(context).how_to_use_card,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: merchant.instructions
.map((instruction) {
return [
Padding(
padding: EdgeInsets.all(10),
child: Text(
instruction.header,
style: textLargeSemiBold(
color: Theme.of(context).textTheme.display2.color,
),
)),
Text(
instruction.body,
style: textMedium(
color: Theme.of(context).textTheme.display2.color,
),
)
];
})
.expand((e) => e)
.toList()),
actionTitle: S.current.send_got_it,
);
});
}
Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
final amount = ioniaPurchaseViewModel.invoice.totalAmount;
final addresses = ioniaPurchaseViewModel.invoice.outAddresses;
await showPopUp<void>(
context: context,
builder: (_) {
return IoniaConfirmModal(
alertTitle: S.of(context).confirm_sending,
alertContent: Container(
height: 200,
padding: EdgeInsets.all(15),
child: Column(children: [
Row(children: [
Text(S.of(context).payment_id,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none)),
Text(ioniaPurchaseViewModel.invoice.paymentId,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
SizedBox(height: 10),
Row(children: [
Text(S.of(context).amount,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none)),
Text('$amount ${ioniaPurchaseViewModel.invoice.chain}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.spaceBetween),
SizedBox(height: 25),
Row(children: [
Text(S.of(context).recipient_address,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none))
], mainAxisAlignment: MainAxisAlignment.center),
Expanded(
child: ListView.builder(
itemBuilder: (_, int index) {
return Text(addresses[index],
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none));
},
itemCount: addresses.length,
physics: NeverScrollableScrollPhysics()))
])),
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
leftActionColor: Color(0xffFF6600),
rightActionColor: Theme.of(context).accentTextTheme.body2.color,
actionRightButton: () async {
Navigator.of(context).pop();
await ioniaPurchaseViewModel.commitPaymentInvoice();
},
actionLeftButton: () => Navigator.of(context).pop());
},
);
}
}
class TipButtonGroup extends StatelessWidget {
const TipButtonGroup({
Key key,
@required this.selectedTip,
@required this.onSelect,
@required this.tipsList,
}) : super(key: key);
final Function(IoniaTip) onSelect;
final double selectedTip;
final List<IoniaTip> tipsList;
bool _isSelected(double value) => selectedTip == value;
@override
Widget build(BuildContext context) {
return Container(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: tipsList.length,
itemBuilder: (BuildContext context, int index) {
final tip = tipsList[index];
return Padding(
padding: EdgeInsets.only(right: 5),
child: TipButton(
isSelected: _isSelected(tip.percentage),
onTap: () => onSelect(tip),
caption: '${tip.percentage}%',
subTitle: '\$${tip.additionalAmount}',
));
}));
}
}
class TipButton extends StatelessWidget {
const TipButton({
@required this.caption,
this.subTitle,
@required this.onTap,
this.isSelected = false,
});
final String caption;
final String subTitle;
final bool isSelected;
final void Function() onTap;
bool isDark(BuildContext context) => Theme.of(context).brightness == Brightness.dark;
Color captionTextColor(BuildContext context) {
if (isDark(context)) {
return Theme.of(context).primaryTextTheme.title.color;
}
return isSelected
? Theme.of(context).accentTextTheme.title.color
: Theme.of(context).primaryTextTheme.title.color;
}
Color subTitleTextColor(BuildContext context) {
if (isDark(context)) {
return Theme.of(context).primaryTextTheme.title.color;
}
return isSelected
? Theme.of(context).accentTextTheme.title.color
: Theme.of(context).primaryTextTheme.overline.color;
}
Color backgroundColor(BuildContext context) {
if (isDark(context)) {
return isSelected
? null
: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.01);
}
return isSelected
? null
: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1);
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
height: 49,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(caption,
style: textSmallSemiBold(
color: captionTextColor(context))),
if (subTitle != null) ...[
SizedBox(height: 4),
Text(
subTitle,
style: textXxSmallSemiBold(
color: subTitleTextColor(context),
),
),
]
],
),
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: backgroundColor(context),
gradient: isSelected
? LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
),
),
);
}
}
@@ -0,0 +1,185 @@
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/generated/i18n.dart';
class IoniaBuyGiftCardPage extends BasePage {
IoniaBuyGiftCardPage(
this.ioniaBuyCardViewModel,
) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() {
_amountController.addListener(() {
ioniaBuyCardViewModel.onAmountChanged(_amountController.text);
});
}
final IoniaBuyCardViewModel ioniaBuyCardViewModel;
@override
String get title => S.current.enter_amount;
@override
Color get titleColor => Colors.white;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
final TextEditingController _amountController;
final FocusNode _amountFieldFocus;
@override
Widget body(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final merchant = ioniaBuyCardViewModel.ioniaMerchant;
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFieldFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: 150),
BaseTextFormField(
controller: _amountController,
focusNode: _amountFieldFocus,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
WhitelistingTextInputFormatter(RegExp(r'^\d+(\.|\,)?\d{0,2}'))],
hintText: '1000',
placeholderTextStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
fontWeight: FontWeight.w600,
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.w600,
fontSize: 36,
),
),
),
),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).min_amount(merchant.minimumCardPurchase.toStringAsFixed(2)),
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
),
),
Text(
S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)),
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
),
),
],
),
SizedBox(height: 24),
],
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: CardItem(
title: merchant.legalName,
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
discount: merchant.discount,
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
subtitleColor: Theme.of(context).hintColor,
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
logoUrl: merchant.logoUrl,
),
)
],
),
bottomSection: Column(
children: [
Observer(builder: (_) {
return Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(
Routes.ioniaBuyGiftCardDetailPage,
arguments: [
ioniaBuyCardViewModel.amount,
ioniaBuyCardViewModel.ioniaMerchant,
],
),
text: S.of(context).continue_text,
isDisabled: !ioniaBuyCardViewModel.isEnablePurchase,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
);
}),
SizedBox(height: 30),
],
),
),
),
);
}
}
@@ -0,0 +1,176 @@
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/generated/i18n.dart';
class IoniaCustomTipPage extends BasePage {
IoniaCustomTipPage(
this.ioniaPurchaseViewModel,
) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() {
_amountController.addListener(() {
// ioniaPurchaseViewModel.onTipChanged(_amountController.text);
});
}
final IoniaMerchPurchaseViewModel ioniaPurchaseViewModel;
@override
String get title => S.current.enter_amount;
@override
Color get titleColor => Colors.white;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
Color get textColor => currentTheme.type == ThemeType.dark ? Colors.white : Color(0xff393939);
final TextEditingController _amountController;
final FocusNode _amountFieldFocus;
@override
Widget body(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final merchant = ioniaPurchaseViewModel.ioniaMerchant;
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFieldFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
content: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 150),
BaseTextFormField(
controller: _amountController,
focusNode: _amountFieldFocus,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\-|\ ]'))],
hintText: '1000',
placeholderTextStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
fontWeight: FontWeight.w500,
fontSize: 36,
),
borderColor: Theme.of(context).primaryTextTheme.headline.color,
textColor: Colors.white,
textStyle: TextStyle(
color: Colors.white,
fontSize: 36,
),
suffixIcon: SizedBox(
width: _width / 6,
),
prefixIcon: Padding(
padding: EdgeInsets.only(
top: 5.0,
left: _width / 4,
),
child: Text(
'USD: ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 36,
),
),
),
),
SizedBox(height: 8),
Observer(builder: (_) {
if (ioniaPurchaseViewModel.percentage == 0.0) {
return SizedBox.shrink();
}
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: '\$${_amountController.text}',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
),
children: [
TextSpan(text: ' ${S.of(context).is_percentage} '),
TextSpan(text: '${ioniaPurchaseViewModel.percentage}%'),
TextSpan(text: ' ${S.of(context).percentageOf(ioniaPurchaseViewModel.amount.toString())} '),
],
),
);
}),
SizedBox(height: 24),
],
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: CardItem(
title: merchant.legalName,
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
discount: 0.0,
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
subtitleColor: Theme.of(context).hintColor,
subTitle: merchant.isOnline ? S.of(context).online : S.of(context).offline,
logoUrl: merchant.logoUrl,
),
)
],
),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () {
Navigator.of(context).pop(_amountController.text);
},
text: S.of(context).add_tip,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(height: 30),
],
),
),
),
);
}
}
@@ -0,0 +1,379 @@
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_gift_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaDebitCardPage extends BasePage {
final IoniaGiftCardsListViewModel _cardsListViewModel;
IoniaDebitCardPage(this._cardsListViewModel);
@override
Widget middle(BuildContext context) {
return Text(
S.current.debit_card,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
return Observer(
builder: (_) {
final cardState = _cardsListViewModel.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),
),
],
);
}
}
@@ -0,0 +1,188 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/ionia/ionia_gift_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/ionia_alert_model.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_tile.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/text_icon_button.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/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_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaGiftCardDetailPage extends BasePage {
IoniaGiftCardDetailPage(this.viewModel);
final IoniaGiftCardDetailsViewModel viewModel;
@override
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),
),
),
);
}
@override
Widget middle(BuildContext context) {
return Text(
viewModel.giftCard.legalName,
style: textMediumSemiBold(color: Theme.of(context).accentTextTheme.display4.backgroundColor),
);
}
@override
Widget body(BuildContext context) {
reaction((_) => viewModel.redeemState, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
if (viewModel.giftCard.barcodeUrl != null && viewModel.giftCard.barcodeUrl.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 24,
),
child: Image.network(viewModel.giftCard.barcodeUrl),
),
SizedBox(height: 24),
buildIoniaTile(
context,
title: S.of(context).gift_card_number,
subTitle: viewModel.giftCard.cardNumber,
),
if (viewModel.giftCard.cardPin?.isNotEmpty ?? false)
...[Divider(height: 30),
buildIoniaTile(
context,
title: S.of(context).pin_number,
subTitle: viewModel.giftCard.cardPin,
)],
Divider(height: 30),
Observer(builder: (_) =>
buildIoniaTile(
context,
title: S.of(context).amount,
subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00',
)),
Divider(height: 50),
TextIconButton(
label: S.of(context).how_to_use_card,
onTap: () => _showHowToUseCard(context, viewModel.giftCard),
),
],
),
bottomSection: Padding(
padding: EdgeInsets.only(bottom: 12),
child: Observer(builder: (_) {
if (!viewModel.giftCard.isEmpty) {
return LoadingPrimaryButton(
isLoading: viewModel.redeemState is IsExecutingState,
onPressed: () => viewModel.redeem().then((_){
Navigator.of(context).pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
}),
text: S.of(context).mark_as_redeemed,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white);
}
return Container();
})),
);
}
Widget buildIoniaTile(BuildContext context, {@required String title, @required String subTitle}) {
return IoniaTile(
title: title,
subTitle: subTitle,
onTap: () {
Clipboard.setData(ClipboardData(text: subTitle));
showBar<void>(context,
S.of(context).transaction_details_copied(title));
});
}
void _showHowToUseCard(
BuildContext context,
IoniaGiftCard merchant,
) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return IoniaAlertModal(
title: S.of(context).how_to_use_card,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: viewModel.giftCard.instructions
.map((instruction) {
return [
Padding(
padding: EdgeInsets.all(10),
child: Text(
instruction.header,
style: textLargeSemiBold(
color: Theme.of(context).textTheme.display2.color,
),
)),
Text(
instruction.body,
style: textMedium(
color: Theme.of(context).textTheme.display2.color,
),
)
];
})
.expand((e) => e)
.toList()),
actionTitle: S.of(context).send_got_it,
);
});
}
}
@@ -0,0 +1,340 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_merchant.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/card_menu.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/ionia_filter_modal.dart';
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaManageCardsPage extends BasePage {
IoniaManageCardsPage(this._cardsListViewModel) {
_searchController.addListener(() {
if (_searchController.text != _cardsListViewModel.searchString) {
_searchDebounce.run(() {
_cardsListViewModel.searchMerchant(_searchController.text);
});
}
});
}
final IoniaGiftCardsListViewModel _cardsListViewModel;
final _searchDebounce = Debounce(Duration(milliseconds: 500));
final _searchController = TextEditingController();
@override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@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: Theme.of(context).accentTextTheme.display3.backgroundColor,
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.pop(context),
child: _backButton),
),
);
}
@override
Widget middle(BuildContext context) {
return Text(
S.of(context).gift_cards,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
),
);
}
@override
Widget trailing(BuildContext context) {
return _TrailingIcon(
asset: 'assets/images/profile.png',
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
);
}
@override
Widget body(BuildContext context) {
final filterIcon = InkWell(
onTap: () async {
final selectedFilters = await showCategoryFilter(context, _cardsListViewModel);
_cardsListViewModel.setSelectedFilter(selectedFilters);
},
child: Image.asset(
'assets/images/filter.png',
color: Theme.of(context).textTheme.caption.decorationColor,
));
return Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
children: [
Container(
padding: EdgeInsets.only(left: 2, right: 22),
height: 32,
child: Row(
children: [
Expanded(
child: _SearchWidget(
controller: _searchController,
)),
SizedBox(width: 10),
Container(
width: 32,
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
border: Border.all(
color: Colors.white.withOpacity(0.2),
),
borderRadius: BorderRadius.circular(10),
),
child: filterIcon,
)
],
),
),
SizedBox(height: 8),
Expanded(
child: IoniaManageCardsPageBody(
cardsListViewModel: _cardsListViewModel,
),
),
],
),
);
}
Future<List<IoniaCategory>> showCategoryFilter(
BuildContext context,
IoniaGiftCardsListViewModel viewModel,
) async {
return await showPopUp<List<IoniaCategory>>(
context: context,
builder: (BuildContext context) {
return IoniaFilterModal(
filterViewModel: getIt.get<IoniaFilterViewModel>(),
selectedCategories: viewModel.selectedFilters,
);
},
);
}
}
class IoniaManageCardsPageBody extends StatefulWidget {
const IoniaManageCardsPageBody({
Key key,
@required this.cardsListViewModel,
}) : super(key: key);
final IoniaGiftCardsListViewModel cardsListViewModel;
@override
_IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
}
class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
double get backgroundHeight => MediaQuery.of(context).size.height * 0.75;
double thumbHeight = 72;
bool get isAlwaysShowScrollThumb => merchantsList == null ? false : merchantsList.length > 3;
List<IoniaMerchant> get merchantsList => widget.cardsListViewModel.ioniaMerchants;
final _scrollController = ScrollController();
@override
void initState() {
_scrollController.addListener(() {
final scrollOffsetFromTop = _scrollController.hasClients
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
: 0.0;
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Observer(
builder: (_) => Stack(children: [
ListView.separated(
padding: EdgeInsets.only(left: 2, right: 22),
controller: _scrollController,
itemCount: merchantsList.length,
separatorBuilder: (_, __) => SizedBox(height: 4),
itemBuilder: (_, index) {
final merchant = merchantsList[index];
var subTitle = '';
if (merchant.isOnline) {
subTitle += S.of(context).online;
}
if (merchant.isPhysical) {
if (subTitle.isNotEmpty) {
subTitle = '$subTitle & ';
}
subTitle = '${subTitle}${S.of(context).in_store}';
}
return CardItem(
logoUrl: merchant.logoUrl,
onTap: () {
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
},
title: merchant.legalName,
subTitle: subTitle,
backgroundColor: Theme.of(context).textTheme.title.backgroundColor,
titleColor: Theme.of(context).accentTextTheme.display3.backgroundColor,
subtitleColor: Theme.of(context).accentTextTheme.display2.backgroundColor,
discount: merchant.discount,
);
},
),
isAlwaysShowScrollThumb
? CakeScrollbar(
backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight,
rightOffset: 1,
width: 3,
backgroundColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.05),
thumbColor: Theme.of(context).textTheme.caption.decorationColor.withOpacity(0.5),
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
)
: Offstage()
]),
);
}
}
class _SearchWidget extends StatelessWidget {
const _SearchWidget({
Key key,
@required this.controller,
}) : super(key: key);
final TextEditingController controller;
@override
Widget build(BuildContext context) {
final searchIcon = Padding(
padding: EdgeInsets.all(8),
child: Image.asset(
'assets/images/mini_search_icon.png',
color: Theme.of(context).textTheme.caption.decorationColor,
),
);
return TextField(
style: TextStyle(color: Colors.white),
controller: controller,
decoration: InputDecoration(
filled: true,
contentPadding: EdgeInsets.only(
top: 10,
left: 10,
),
fillColor: Colors.white.withOpacity(0.15),
hintText: S.of(context).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,
),
),
);
}
}
@@ -0,0 +1,217 @@
import 'package:cake_wallet/ionia/ionia_gift_card.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/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class IoniaPaymentStatusPage extends BasePage {
IoniaPaymentStatusPage(this.viewModel);
final IoniaPaymentStatusViewModel viewModel;
@override
Widget middle(BuildContext context) {
return Text(
S.of(context).generating_gift_card,
textAlign: TextAlign.center,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor));
}
@override
Widget body(BuildContext context) {
return _IoniaPaymentStatusPageBody(viewModel);
}
}
class _IoniaPaymentStatusPageBody extends StatefulWidget {
_IoniaPaymentStatusPageBody(this.viewModel);
final IoniaPaymentStatusViewModel viewModel;
@override
_IoniaPaymentStatusPageBodyBodyState createState() => _IoniaPaymentStatusPageBodyBodyState();
}
class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPageBody> {
ReactionDisposer _onGiftCardReaction;
@override
void initState() {
if (widget.viewModel.giftCard != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context)
.pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [widget.viewModel.giftCard]);
});
}
_onGiftCardReaction = reaction((_) => widget.viewModel.giftCard, (IoniaGiftCard giftCard) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context)
.pushReplacementNamed(Routes.ioniaGiftCardDetailPage, arguments: [giftCard]);
});
});
super.initState();
}
@override
void dispose() {
_onGiftCardReaction?.reaction?.dispose();
widget.viewModel.timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(children: [
Padding(
padding: EdgeInsets.only(right: 10),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.green),
height: 10,
width: 10)),
Text(
S.of(context).awaiting_payment_confirmation,
style: textLargeSemiBold(
color: Theme.of(context).primaryTextTheme.title.color))
]),
SizedBox(height: 40),
Row(children: [
SizedBox(width: 20),
Expanded(child:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
...widget.viewModel
.committedInfo
.transactions
.map((transaction) => buildDescriptionTileWithCopy(context, S.of(context).transaction_details_transaction_id, transaction.id)),
Divider(height: 30),
buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id),
Divider(height: 30),
buildDescriptionTileWithCopy(context, S.of(context).payment_id, widget.viewModel.paymentInfo.ioniaOrder.paymentId),
]))
]),
SizedBox(height: 40),
Observer(builder: (_) {
if (widget.viewModel.giftCard != null) {
return Container(
padding: EdgeInsets.only(top: 40),
child: Row(children: [
Padding(
padding: EdgeInsets.only(right: 10,),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.green),
height: 10,
width: 10)),
Text(
S.of(context).gift_card_is_generated,
style: textLargeSemiBold(
color: Theme.of(context).primaryTextTheme.title.color))
]));
}
return Row(children: [
Padding(
padding: EdgeInsets.only(right: 10),
child: Observer(builder: (_) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: widget.viewModel.giftCard == null ? Colors.grey : Colors.green),
height: 10,
width: 10);
})),
Text(
S.of(context).generating_gift_card,
style: textLargeSemiBold(
color: Theme.of(context).primaryTextTheme.title.color))]);
}),
],
),
bottomSection: Padding(
padding: EdgeInsets.only(bottom: 12),
child: Column(children: [
Container(
padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
child: Text(
S.of(context).proceed_after_one_minute,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
).copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
)),
Observer(builder: (_) {
if (widget.viewModel.giftCard != null) {
return PrimaryButton(
onPressed: () => Navigator.of(context)
.pushReplacementNamed(
Routes.ioniaGiftCardDetailPage,
arguments: [widget.viewModel.giftCard]),
text: S.of(context).open_gift_card,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white);
}
return PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(Routes.support),
text: S.of(context).contact_support,
color: Theme.of(context).accentTextTheme.caption.color,
textColor: Theme.of(context).primaryTextTheme.title.color);
})
])
),
);
}
Widget buildDescriptionTile(BuildContext context, String title, String subtitle, VoidCallback onTap) {
return GestureDetector(
onTap: () => onTap(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textXSmall(
color: Theme.of(context).primaryTextTheme.overline.color,
),
),
SizedBox(height: 8),
Text(
subtitle,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
),
),
],
));
}
Widget buildDescriptionTileWithCopy(BuildContext context, String title, String subtitle) {
return buildDescriptionTile(context, title, subtitle, () {
Clipboard.setData(ClipboardData(text: subtitle));
showBar<void>(context,
S.of(context).transaction_details_copied(title));
});
}
}
+9
View 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';
@@ -0,0 +1,139 @@
import 'package:cake_wallet/src/widgets/discount_badge.dart';
import 'package:flutter/material.dart';
class CardItem extends StatelessWidget {
CardItem({
@required this.title,
@required this.subTitle,
@required this.backgroundColor,
@required this.titleColor,
@required this.subtitleColor,
this.discountBackground,
this.onTap,
this.logoUrl,
this.discount,
});
final VoidCallback onTap;
final String title;
final String subTitle;
final String logoUrl;
final double discount;
final Color backgroundColor;
final Color titleColor;
final Color subtitleColor;
final AssetImage discountBackground;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Stack(
children: [
Container(
padding: EdgeInsets.all(12),
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor,
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: 40.0,
height: 40.0,
fit: BoxFit.cover,
loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent loadingProgress) {
if (loadingProgress == null) {
return child;
} else {
return _PlaceholderContainer(text: 'Logo');
}
},
errorBuilder: (_, __, ___) => _PlaceholderContainer(text: '!'),
),
),
SizedBox(width: 5),
],
Column(
crossAxisAlignment: (subTitle?.isEmpty ?? false)
? CrossAxisAlignment.center
: CrossAxisAlignment.start,
children: [
SizedBox(
width: 200,
child: Text(
title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: titleColor,
fontSize: 20,
fontWeight: FontWeight.w900,
),
),
),
if (subTitle?.isNotEmpty ?? false)
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
subTitle,
style: TextStyle(
color: subtitleColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato')),
)
],
),
],
),
),
if (discount != 0.0)
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: DiscountBadge(
percentage: discount,
discountBackground: discountBackground,
),
),
),
],
),
);
}
}
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),
),
);
}
}
@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class CardMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
}
}
@@ -0,0 +1,148 @@
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,
this.hideActions = false,
});
final String alertTitle;
final Widget alertContent;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
final VoidCallback actionRightButton;
final Color leftActionColor;
final Color rightActionColor;
final bool hideActions;
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,
),
)),
),
));
}
}
@@ -0,0 +1,86 @@
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart';
class IoniaAlertModal extends StatelessWidget {
const IoniaAlertModal({
Key key,
@required this.title,
@required this.content,
@required this.actionTitle,
this.heightFactor = 0.4,
this.showCloseButton = true,
}) : super(key: key);
final String title;
final Widget content;
final String actionTitle;
final bool showCloseButton;
final double heightFactor;
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Material(
color: Colors.transparent,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Spacer(),
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: [
if (title.isNotEmpty)
Text(
title,
style: textLargeSemiBold(
color: Theme.of(context).textTheme.body1.color,
),
),
Container(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * heightFactor),
child: ListView(
children: [
content,
SizedBox(height: 35),
],
),
),
PrimaryButton(
onPressed: () => Navigator.pop(context),
text: actionTitle,
color: Theme.of(context).accentTextTheme.caption.color,
textColor: Theme.of(context).primaryTextTheme.title.color,
),
SizedBox(height: 21),
],
),
),
Spacer(),
if(showCloseButton)
InkWell(
onTap: () => Navigator.pop(context),
child: Container(
margin: EdgeInsets.only(bottom: 40),
child: CircleAvatar(
child: Icon(
Icons.close,
color: Colors.black,
),
backgroundColor: Colors.white,
),
),
)
],
),
),
);
}
}
@@ -0,0 +1,134 @@
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class IoniaFilterModal extends StatelessWidget {
IoniaFilterModal({
@required this.filterViewModel,
@required this.selectedCategories,
}) {
filterViewModel.setSelectedCategories(this.selectedCategories);
}
final IoniaFilterViewModel filterViewModel;
final List<IoniaCategory> selectedCategories;
@override
Widget build(BuildContext context) {
final searchIcon = Padding(
padding: EdgeInsets.all(10),
child: Image.asset(
'assets/images/mini_search_icon.png',
color: Theme.of(context).accentColor,
),
);
return Scaffold(
resizeToAvoidBottomInset: false,
body: AlertBackground(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: 10),
Container(
padding: EdgeInsets.only(top: 24, bottom: 20),
margin: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(30),
),
child: Column(
children: [
SizedBox(
height: 40,
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: TextField(
onChanged: filterViewModel.onSearchFilter,
style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
),
decoration: InputDecoration(
filled: true,
prefixIcon: searchIcon,
hintText: S.of(context).search_category,
contentPadding: EdgeInsets.only(bottom: 5),
fillColor: Theme.of(context).textTheme.subhead.backgroundColor,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
SizedBox(height: 10),
Divider(thickness: 2),
SizedBox(height: 24),
Observer(builder: (_) {
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: filterViewModel.ioniaCategories.length,
itemBuilder: (_, index) {
final category = filterViewModel.ioniaCategories[index];
return Padding(
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: InkWell(
onTap: () => filterViewModel.selectFilter(category),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
category.iconPath,
color: Theme.of(context).primaryTextTheme.title.color,
),
SizedBox(width: 10),
Text(category.title,
style: textSmall(
color: Theme.of(context).primaryTextTheme.title.color,
).copyWith(fontWeight: FontWeight.w500)),
],
),
Observer(builder: (_) {
final value = filterViewModel.selectedIndices;
return RoundedCheckbox(
value: value.contains(category.index),
);
}),
],
),
),
);
},
);
}),
],
),
),
InkWell(
onTap: () => Navigator.pop(context, filterViewModel.selectedCategories),
child: Container(
margin: EdgeInsets.only(bottom: 40),
child: CircleAvatar(
child: Icon(
Icons.close,
color: Colors.black,
),
backgroundColor: Colors.white,
),
),
)
],
),
),
);
}
}
@@ -0,0 +1,44 @@
import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart';
class IoniaTile extends StatelessWidget {
const IoniaTile({
Key key,
@required this.title,
@required this.subTitle,
this.onTap,
}) : super(key: key);
final VoidCallback onTap;
final String title;
final String subTitle;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textXSmall(
color: Theme.of(context).primaryTextTheme.overline.color,
),
),
SizedBox(height: 8),
Text(
subTitle,
style: textMediumBold(
color: Theme.of(context).primaryTextTheme.title.color,
),
),
],
)
],
));
}
}
@@ -0,0 +1,27 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RoundedCheckbox extends StatelessWidget {
RoundedCheckbox({Key key, @required this.value}) : super(key: key);
final bool value;
@override
Widget build(BuildContext context) {
return value
? Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
color: Theme.of(context).accentTextTheme.body2.color,
),
child: Icon(
Icons.check,
color: Theme.of(context).backgroundColor,
size: 14.0,
))
: Offstage();
}
}
@@ -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,
),
],
),
);
}
}
+14 -2
View File
@@ -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;
}
+54 -60
View File
@@ -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 {
),
);
}
}
}
+15 -12
View File
@@ -5,13 +5,19 @@ class CakeScrollbar extends StatelessWidget {
@required this.backgroundHeight,
@required this.thumbHeight,
@required this.fromTop,
this.rightOffset = 6
this.rightOffset = 6,
this.backgroundColor,
this.thumbColor,
this.width = 6,
});
final double backgroundHeight;
final double thumbHeight;
final double fromTop;
final double width;
final double rightOffset;
final Color backgroundColor;
final Color thumbColor;
@override
Widget build(BuildContext context) {
@@ -19,11 +25,10 @@ class CakeScrollbar extends StatelessWidget {
right: rightOffset,
child: Container(
height: backgroundHeight,
width: 6,
width: width,
decoration: BoxDecoration(
color: Theme.of(context).textTheme.body1.decorationColor,
borderRadius: BorderRadius.all(Radius.circular(3))
),
color: backgroundColor ?? Theme.of(context).textTheme.body1.decorationColor,
borderRadius: BorderRadius.all(Radius.circular(3))),
child: Stack(
children: <Widget>[
AnimatedPositioned(
@@ -31,16 +36,14 @@ class CakeScrollbar extends StatelessWidget {
top: fromTop,
child: Container(
height: thumbHeight,
width: 6.0,
width: width,
decoration: BoxDecoration(
color: Theme.of(context).textTheme.body1.color,
borderRadius: BorderRadius.all(Radius.circular(3))
),
color: thumbColor ?? Theme.of(context).textTheme.body1.color,
borderRadius: BorderRadius.all(Radius.circular(3))),
),
)
],
),
)
);
));
}
}
}
+35
View File
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
class DiscountBadge extends StatelessWidget {
const DiscountBadge({
Key key,
@required this.percentage,
this.discountBackground,
}) : super(key: key);
final double percentage;
final AssetImage discountBackground;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
S.of(context).discount(percentage.toStringAsFixed(2)),
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
),
),
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: discountBackground ?? AssetImage('assets/images/badge_discount.png'),
),
),
);
}
}
+66
View 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'),
)
],
),
),
],
),
);
}
}