CW-148 cake pay partial redemptions (#490)

* Ionia custom redemption screen

* update ionia gift card remaining amount with custom value

* [NO-TASK] Fix issues with custom redeem

* replace redeem

* update remaining amount

* fixed from code review

* Add localization
This commit is contained in:
Godwin Asuquo
2022-09-02 14:47:45 +01:00
committed by GitHub
parent 0aee6e1b8d
commit bd25d047b2
26 changed files with 446 additions and 32 deletions
+18
View File
@@ -5,10 +5,13 @@ import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_custom_redeem_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_filter_view_model.dart';
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:cake_wallet/ionia/ionia_api.dart';
@@ -747,6 +750,21 @@ Future setup(
return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
});
getIt.registerFactoryParam<IoniaMoreOptionsPage, List, void>((List args, _){
final giftCard = args.first as IoniaGiftCard;
return IoniaMoreOptionsPage(giftCard);
});
getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) => IoniaCustomRedeemViewModel(giftCard));
getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _){
final giftCard = args.first as IoniaGiftCard;
return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard) );
});
getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
return IoniaCustomTipPage(getIt.get<IoniaCustomTipViewModel>(param1: args));
});
+2 -1
View File
@@ -60,10 +60,11 @@ class IoniaGiftCard {
final double actualAmount;
final double totalTransactionAmount;
final double totalDashTransactionAmount;
final double remainingAmount;
double remainingAmount;
final String createdDateFormatted;
final String lastTransactionDateFormatted;
final bool isActive;
final bool isEmpty;
final String logoUrl;
}
+10
View File
@@ -6,8 +6,10 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
@@ -452,6 +454,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaGiftCardDetailPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
case Routes.ioniaCustomRedeemPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
case Routes.ioniaMoreOptionsPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
case Routes.ioniaPaymentStatusPage:
final args = settings.arguments as List;
+2
View File
@@ -74,4 +74,6 @@ class Routes {
static const ioniaCustomTipPage = 'ionia_custom_tip_page';
static const ioniaGiftCardDetailPage = '/ionia_gift_card_detail_page';
static const ioniaPaymentStatusPage = '/ionia_payment_status_page';
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
}
@@ -0,0 +1,167 @@
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_custom_redeem_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 IoniaCustomRedeemPage extends BasePage {
IoniaCustomRedeemPage(
this.ioniaCustomRedeemViewModel,
) : _amountFieldFocus = FocusNode(),
_amountController = TextEditingController() {
_amountController.addListener(() {
ioniaCustomRedeemViewModel.updateAmount(_amountController.text);
});
}
final IoniaCustomRedeemViewModel ioniaCustomRedeemViewModel;
@override
String get title => S.current.custom_redeem_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 giftCard = ioniaCustomRedeemViewModel.giftCard;
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: (_)=>
!ioniaCustomRedeemViewModel.disableRedeem ?
Center(
child: Text('\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.remaining} ${S.of(context).remaining}',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline.color,
),),
) : SizedBox.shrink(),
),
SizedBox(height: 24),
],
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: CardItem(
title: giftCard.legalName,
backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1),
discount: giftCard.remainingAmount,
isAmount: true,
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor,
subtitleColor: Theme.of(context).hintColor,
subTitle: S.of(context).online,
logoUrl: giftCard.logoUrl,
),
),
],
),
bottomSection: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () {
Navigator.of(context).pop(_amountController.text);
},
isDisabled: ioniaCustomRedeemViewModel.disableRedeem,
text: S.of(context).add_custom_redemption,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(height: 30),
],
),
),
),
);
}
}
@@ -117,7 +117,7 @@ class IoniaGiftCardDetailPage extends BasePage {
buildIoniaTile(
context,
title: S.of(context).amount,
subTitle: viewModel.giftCard.remainingAmount.toStringAsFixed(2) ?? '0.00',
subTitle: viewModel.remainingAmount.toStringAsFixed(2) ?? '0.00',
)),
Divider(height: 50),
TextIconButton(
@@ -127,21 +127,45 @@ class IoniaGiftCardDetailPage extends BasePage {
],
),
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);
}
padding: EdgeInsets.only(bottom: 12),
child: Observer(
builder: (_) {
if (!viewModel.giftCard.isEmpty) {
return Column(
children: [
PrimaryButton(
onPressed: () async {
final amount = await Navigator.of(context)
.pushNamed(Routes.ioniaMoreOptionsPage, arguments: [viewModel.giftCard]) as String;
if (amount != null) {
viewModel.updateRemaining(double.parse(amount));
}
},
text: S.of(context).more_options,
color: Theme.of(context).accentTextTheme.caption.color,
textColor: Theme.of(context).primaryTextTheme.title.color,
),
SizedBox(height: 12),
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();
})),
return Container();
},
),
),
);
}
@@ -0,0 +1,90 @@
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/generated/i18n.dart';
import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart';
class IoniaMoreOptionsPage extends BasePage {
IoniaMoreOptionsPage(this.giftCard);
final IoniaGiftCard giftCard;
@override
Widget middle(BuildContext context) {
return Text(
S.current.more_options,
style: textMediumSemiBold(
color: Theme.of(context).accentTextTheme.display4.backgroundColor,
),
);
}
@override
Widget body(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 10,),
Center(child: Text(S.of(context).choose_from_available_options, style: textMedium(
color: Theme.of(context).primaryTextTheme.title.color,
),)),
SizedBox(height: 40,),
InkWell(
onTap: () async {
final amount = await Navigator.of(context).pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String;
if(amount.isNotEmpty){
Navigator.pop(context, amount);
}
},
child: _GradiantContainer(
content: Padding(
padding: const EdgeInsets.only(top: 24, left: 20, right: 24, bottom: 50),
child: Text(
S.of(context).custom_redeem_amount,
style: textXLargeSemiBold(),
),
),
),
)
],
),
);
}
}
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,
),
),
);
}
}
@@ -12,6 +12,7 @@ class CardItem extends StatelessWidget {
this.onTap,
this.logoUrl,
this.discount,
this.isAmount = false,
});
final VoidCallback onTap;
@@ -19,6 +20,7 @@ class CardItem extends StatelessWidget {
final String subTitle;
final String logoUrl;
final double discount;
final bool isAmount;
final Color backgroundColor;
final Color titleColor;
final Color subtitleColor;
@@ -100,6 +102,7 @@ class CardItem extends StatelessWidget {
padding: const EdgeInsets.only(top: 20.0),
child: DiscountBadge(
percentage: discount,
isAmount: isAmount,
discountBackground: discountBackground,
),
),
+3 -1
View File
@@ -4,11 +4,13 @@ import 'package:cake_wallet/generated/i18n.dart';
class DiscountBadge extends StatelessWidget {
const DiscountBadge({
Key key,
this.isAmount = false,
@required this.percentage,
this.discountBackground,
}) : super(key: key);
final double percentage;
final bool isAmount;
final AssetImage discountBackground;
@override
@@ -16,7 +18,7 @@ class DiscountBadge extends StatelessWidget {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
S.of(context).discount(percentage.toStringAsFixed(2)),
isAmount ? '\$${percentage.toStringAsFixed(2)}' : S.of(context).discount(percentage.toStringAsFixed(2)),
style: TextStyle(
color: Colors.white,
fontSize: 12,
@@ -0,0 +1,27 @@
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:mobx/mobx.dart';
part 'ionia_custom_redeem_view_model.g.dart';
class IoniaCustomRedeemViewModel = IoniaCustomRedeemViewModelBase with _$IoniaCustomRedeemViewModel;
abstract class IoniaCustomRedeemViewModelBase with Store {
IoniaCustomRedeemViewModelBase(this.giftCard){
amount = 0;
}
final IoniaGiftCard giftCard;
@observable
double amount;
@computed
double get remaining => amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0;
@computed
bool get disableRedeem => amount > giftCard.remainingAmount;
@action
void updateAmount(String text){
amount = text.isEmpty ? 0 : (double.parse(text.replaceAll(',', '.')) ?? 0);
}
}
@@ -12,6 +12,7 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store {
IoniaGiftCardDetailsViewModelBase({this.ioniaService, this.giftCard}) {
redeemState = InitialExecutionState();
remainingAmount = giftCard.remainingAmount;
}
final IoniaService ioniaService;
@@ -20,11 +21,15 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store {
@observable
IoniaGiftCard giftCard;
@observable
double remainingAmount;
@observable
ExecutionState redeemState;
@action
Future<void> redeem() async {
giftCard.remainingAmount = remainingAmount;
try {
redeemState = IsExecutingState();
await ioniaService.redeem(giftCard);
@@ -35,6 +40,11 @@ abstract class IoniaGiftCardDetailsViewModelBase with Store {
}
}
@action
void updateRemaining(double amount){
remainingAmount = amount;
}
void increaseBrightness() async {
brightness = await DeviceDisplayBrightness.getBrightness();
await DeviceDisplayBrightness.setBrightness(1.0);