mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-27 06:18:50 +01:00
Merge branch 'main' into CW-621-Open-external-sites-in-default-browser
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/route_aware.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
@@ -6,7 +7,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/src/widgets/nav_bar.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
enum AppBarStyle { regular, withShadow, transparent }
|
||||
enum AppBarStyle { regular, withShadow, transparent, completelyTransparent }
|
||||
|
||||
abstract class BasePage extends StatelessWidget {
|
||||
BasePage() : _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
@@ -32,6 +33,14 @@ abstract class BasePage extends StatelessWidget {
|
||||
|
||||
Widget? get endDrawer => null;
|
||||
|
||||
Function(BuildContext context)? get pushToWidget => null;
|
||||
|
||||
Function(BuildContext context)? get pushToNextWidget => null;
|
||||
|
||||
Function(BuildContext context)? get popWidget => null;
|
||||
|
||||
Function(BuildContext context)? get popNextWidget => null;
|
||||
|
||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||
|
||||
Widget Function(BuildContext, Widget)? get rootWrapper => null;
|
||||
@@ -116,7 +125,7 @@ abstract class BasePage extends StatelessWidget {
|
||||
|
||||
Widget? floatingActionButton(BuildContext context) => null;
|
||||
|
||||
ObstructingPreferredSizeWidget appBar(BuildContext context) {
|
||||
PreferredSizeWidget appBar(BuildContext context) {
|
||||
final appBarColor = pageBackgroundColor(context);
|
||||
|
||||
switch (appBarStyle) {
|
||||
@@ -147,6 +156,16 @@ abstract class BasePage extends StatelessWidget {
|
||||
border: null,
|
||||
);
|
||||
|
||||
case AppBarStyle.completelyTransparent:
|
||||
return AppBar(
|
||||
leading: leading(context),
|
||||
title: middle(context),
|
||||
actions: <Widget>[if (trailing(context) != null) trailing(context)!],
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
);
|
||||
|
||||
default:
|
||||
// FIX-ME: NavBar no context
|
||||
return NavBar(
|
||||
@@ -162,15 +181,21 @@ abstract class BasePage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final root = Scaffold(
|
||||
key: _scaffoldKey,
|
||||
backgroundColor: pageBackgroundColor(context),
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
extendBodyBehindAppBar: extendBodyBehindAppBar,
|
||||
endDrawer: endDrawer,
|
||||
appBar: appBar(context),
|
||||
body: body(context),
|
||||
floatingActionButton: floatingActionButton(context));
|
||||
final root = RouteAwareWidget(
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
backgroundColor: pageBackgroundColor(context),
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
extendBodyBehindAppBar: extendBodyBehindAppBar,
|
||||
endDrawer: endDrawer,
|
||||
appBar: appBar(context),
|
||||
body: body(context),
|
||||
floatingActionButton: floatingActionButton(context)),
|
||||
pushToWidget: (context) => pushToWidget?.call(context),
|
||||
pushToNextWidget: (context) => pushToNextWidget?.call(context),
|
||||
popWidget: (context) => popWidget?.call(context),
|
||||
popNextWidget: (context) => popNextWidget?.call(context),
|
||||
);
|
||||
|
||||
return rootWrapper?.call(context, root) ?? root;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/src/screens/cake_pay/widgets/cake_pay_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/cake_pay/cake_pay_account_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class CakePayAccountPage extends BasePage {
|
||||
CakePayAccountPage(this.cakePayAccountViewModel);
|
||||
|
||||
final CakePayAccountViewModel cakePayAccountViewModel;
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget leading(BuildContext context) {
|
||||
return MergeSemantics(
|
||||
child: SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: Semantics(
|
||||
label: S.of(context).seed_alert_back,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateColor.resolveWith(
|
||||
(states) => Colors.transparent),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: backButton(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.account,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.all(24),
|
||||
content: Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
Observer(
|
||||
builder: (_) => Container(decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 1.0,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: CakePayTile(title: S.of(context).email_address, subTitle: cakePayAccountViewModel.email)),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.all(30),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
text: S.of(context).logout,
|
||||
onPressed: () {
|
||||
cakePayAccountViewModel.logout();
|
||||
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+18
-22
@@ -1,39 +1,38 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_states.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/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.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:cake_wallet/view_model/cake_pay/cake_pay_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)
|
||||
class CakePayVerifyOtpPage extends BasePage {
|
||||
CakePayVerifyOtpPage(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();
|
||||
if (otp.length > 5) {
|
||||
_authViewModel.otpState = CakePayOtpSendEnabled();
|
||||
} else {
|
||||
_authViewModel.otpState = IoniaOtpSendDisabled();
|
||||
_authViewModel.otpState = CakePayOtpSendDisabled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
final CakePayAuthViewModel _authViewModel;
|
||||
final bool isSignIn;
|
||||
|
||||
final String _email;
|
||||
@@ -53,11 +52,11 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.otpState, (IoniaOtpState state) {
|
||||
if (state is IoniaOtpFailure) {
|
||||
reaction((_) => _authViewModel.otpState, (CakePayOtpState state) {
|
||||
if (state is CakePayOtpFailure) {
|
||||
_onOtpFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaOtpSuccess) {
|
||||
if (state is CakePayOtpSuccess) {
|
||||
_onOtpSuccessful(context);
|
||||
}
|
||||
});
|
||||
@@ -98,9 +97,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||
Text(S.of(context).didnt_get_code),
|
||||
SizedBox(width: 20),
|
||||
InkWell(
|
||||
onTap: () => isSignIn
|
||||
? _authViewModel.signIn(_email)
|
||||
: _authViewModel.createUser(_email),
|
||||
onTap: () => _authViewModel.logIn(_email),
|
||||
child: Text(
|
||||
S.of(context).resend_code,
|
||||
style: textSmallSemiBold(color: Palette.blueCraiola),
|
||||
@@ -120,8 +117,8 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).continue_text,
|
||||
onPressed: _verify,
|
||||
isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled,
|
||||
isLoading: _authViewModel.otpState is IoniaOtpValidating,
|
||||
isDisabled: _authViewModel.otpState is CakePayOtpSendDisabled,
|
||||
isLoading: _authViewModel.otpState is CakePayOtpValidating,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
@@ -149,8 +146,7 @@ class IoniaVerifyIoniaOtp extends BasePage {
|
||||
}
|
||||
|
||||
void _onOtpSuccessful(BuildContext context) =>
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst);
|
||||
Navigator.pop(context);
|
||||
|
||||
void _verify() async => await _authViewModel.verifyEmail(_codeController.text);
|
||||
}
|
||||
+41
-24
@@ -1,22 +1,22 @@
|
||||
import 'package:cake_wallet/core/email_validator.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_states.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/themes/extensions/cake_text_theme.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:cake_wallet/view_model/cake_pay/cake_pay_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)
|
||||
class CakePayWelcomePage extends BasePage {
|
||||
CakePayWelcomePage(this._authViewModel)
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_emailController = TextEditingController() {
|
||||
_emailController.text = _authViewModel.email;
|
||||
@@ -25,14 +25,14 @@ class IoniaLoginPage extends BasePage {
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
final IoniaAuthViewModel _authViewModel;
|
||||
final CakePayAuthViewModel _authViewModel;
|
||||
|
||||
final TextEditingController _emailController;
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.login,
|
||||
S.current.welcome_to_cakepay,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
@@ -41,25 +41,40 @@ class IoniaLoginPage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => _authViewModel.signInState, (IoniaCreateAccountState state) {
|
||||
if (state is IoniaCreateStateFailure) {
|
||||
reaction((_) => _authViewModel.userVerificationState, (CakePayUserVerificationState state) {
|
||||
if (state is CakePayUserVerificationStateFailure) {
|
||||
_onLoginUserFailure(context, state.error);
|
||||
}
|
||||
if (state is IoniaCreateStateSuccess) {
|
||||
if (state is CakePayUserVerificationStateSuccess) {
|
||||
_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,
|
||||
onSubmit: (text) => _login(),
|
||||
),
|
||||
content: Column(
|
||||
children: [
|
||||
SizedBox(height: 90),
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
hintText: S.of(context).email_address,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: EmailValidator(),
|
||||
controller: _emailController,
|
||||
onSubmit: (text) => _login(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
S.of(context).about_cake_pay,
|
||||
style: textLarge(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(S.of(context).cake_pay_account_note,
|
||||
style: textLarge(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
|
||||
bottomSection: Column(
|
||||
@@ -71,7 +86,8 @@ class IoniaLoginPage extends BasePage {
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.of(context).login,
|
||||
onPressed: _login,
|
||||
isLoading: _authViewModel.signInState is IoniaCreateStateLoading,
|
||||
isLoading:
|
||||
_authViewModel.userVerificationState is CakePayUserVerificationStateLoading,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
@@ -98,9 +114,10 @@ class IoniaLoginPage extends BasePage {
|
||||
});
|
||||
}
|
||||
|
||||
void _onLoginSuccessful(BuildContext context, IoniaAuthViewModel authViewModel) => Navigator.pushNamed(
|
||||
void _onLoginSuccessful(BuildContext context, CakePayAuthViewModel authViewModel) =>
|
||||
Navigator.pushReplacementNamed(
|
||||
context,
|
||||
Routes.ioniaVerifyIoniaOtpPage,
|
||||
Routes.cakePayVerifyOtpPage,
|
||||
arguments: [authViewModel.email, true],
|
||||
);
|
||||
|
||||
@@ -108,6 +125,6 @@ class IoniaLoginPage extends BasePage {
|
||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
await _authViewModel.signIn(_emailController.text);
|
||||
await _authViewModel.logIn(_emailController.text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export 'auth/cake_pay_welcome_page.dart';
|
||||
export 'auth/cake_pay_verify_otp_page.dart';
|
||||
export 'cards/cake_pay_confirm_purchase_card_page.dart';
|
||||
export 'cards/cake_pay_cards_page.dart';
|
||||
export 'cards/cake_pay_buy_card_page.dart';
|
||||
@@ -0,0 +1,474 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_payment_credantials.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/image_placeholder.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/link_extractor.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/number_text_fild_widget.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/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.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';
|
||||
|
||||
class CakePayBuyCardPage extends BasePage {
|
||||
CakePayBuyCardPage(
|
||||
this.cakePayBuyCardViewModel,
|
||||
this.cakePayService,
|
||||
) : _amountFieldFocus = FocusNode(),
|
||||
_amountController = TextEditingController(),
|
||||
_quantityFieldFocus = FocusNode(),
|
||||
_quantityController =
|
||||
TextEditingController(text: cakePayBuyCardViewModel.quantity.toString()) {
|
||||
_amountController.addListener(() {
|
||||
cakePayBuyCardViewModel.onAmountChanged(_amountController.text);
|
||||
});
|
||||
}
|
||||
|
||||
final CakePayBuyCardViewModel cakePayBuyCardViewModel;
|
||||
final CakePayService cakePayService;
|
||||
|
||||
@override
|
||||
String get title => cakePayBuyCardViewModel.card.name;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.completelyTransparent;
|
||||
|
||||
@override
|
||||
Widget? middle(BuildContext context) {
|
||||
return Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: titleColor(context)),
|
||||
);
|
||||
}
|
||||
|
||||
final TextEditingController _amountController;
|
||||
final FocusNode _amountFieldFocus;
|
||||
final TextEditingController _quantityController;
|
||||
final FocusNode _quantityFieldFocus;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final card = cakePayBuyCardViewModel.card;
|
||||
final vendor = cakePayBuyCardViewModel.vendor;
|
||||
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
height: responsiveLayoutUtil.screenHeight * 0.35,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(flex: 4, child: const SizedBox()),
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
child: Image.network(
|
||||
card.cardImageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (BuildContext context, Widget child,
|
||||
ImageChunkEvent? loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Center(child: CircularProgressIndicator());
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
CakePayCardImagePlaceholder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: const SizedBox()),
|
||||
],
|
||||
)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Container(
|
||||
height: responsiveLayoutUtil.screenHeight * 0.5,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: Text(S.of(context).enter_amount,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
)),
|
||||
),
|
||||
card.denominations.isNotEmpty
|
||||
? Expanded(
|
||||
flex: 2,
|
||||
child: _DenominationsAmountWidget(
|
||||
fiatCurrency: card.fiatCurrency.title,
|
||||
denominations: card.denominations,
|
||||
amountFieldFocus: _amountFieldFocus,
|
||||
amountController: _amountController,
|
||||
quantityFieldFocus: _quantityFieldFocus,
|
||||
quantityController: _quantityController,
|
||||
onAmountChanged: cakePayBuyCardViewModel.onAmountChanged,
|
||||
onQuantityChanged: cakePayBuyCardViewModel.onQuantityChanged,
|
||||
cakePayBuyCardViewModel: cakePayBuyCardViewModel,
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
flex: 2,
|
||||
child: _EnterAmountWidget(
|
||||
minValue: card.minValue ?? '-',
|
||||
maxValue: card.maxValue ?? '-',
|
||||
fiatCurrency: card.fiatCurrency.title,
|
||||
amountFieldFocus: _amountFieldFocus,
|
||||
amountController: _amountController,
|
||||
onAmountChanged: cakePayBuyCardViewModel.onAmountChanged,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
children: [
|
||||
if (vendor.cakeWarnings != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
vendor.cakeWarnings!,
|
||||
textAlign: TextAlign.center,
|
||||
style: textSmallSemiBold(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: ClickableLinksText(
|
||||
text: card.description ?? '',
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.secondaryTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Observer(builder: (_) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () => navigateToCakePayBuyCardDetailPage(context, card),
|
||||
text: S.of(context).buy_now,
|
||||
isDisabled: !cakePayBuyCardViewModel.isEnablePurchase,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> navigateToCakePayBuyCardDetailPage(BuildContext context, CakePayCard card) async {
|
||||
final userName = await cakePayService.getUserEmail();
|
||||
final paymentCredential = PaymentCredential(
|
||||
amount: cakePayBuyCardViewModel.amount,
|
||||
quantity: cakePayBuyCardViewModel.quantity,
|
||||
totalAmount: cakePayBuyCardViewModel.totalAmount,
|
||||
userName: userName,
|
||||
fiatCurrency: card.fiatCurrency.title,
|
||||
);
|
||||
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.cakePayBuyCardDetailPage,
|
||||
arguments: [paymentCredential, card],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DenominationsAmountWidget extends StatelessWidget {
|
||||
const _DenominationsAmountWidget({
|
||||
required this.fiatCurrency,
|
||||
required this.denominations,
|
||||
required this.amountFieldFocus,
|
||||
required this.amountController,
|
||||
required this.quantityFieldFocus,
|
||||
required this.quantityController,
|
||||
required this.cakePayBuyCardViewModel,
|
||||
required this.onAmountChanged,
|
||||
required this.onQuantityChanged,
|
||||
});
|
||||
|
||||
final String fiatCurrency;
|
||||
final List<String> denominations;
|
||||
final FocusNode amountFieldFocus;
|
||||
final TextEditingController amountController;
|
||||
final FocusNode quantityFieldFocus;
|
||||
final TextEditingController quantityController;
|
||||
final CakePayBuyCardViewModel cakePayBuyCardViewModel;
|
||||
final Function(String) onAmountChanged;
|
||||
final Function(int?) onQuantityChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 12,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DropdownFilterList(
|
||||
items: denominations,
|
||||
itemPrefix: fiatCurrency,
|
||||
selectedItem: denominations.first,
|
||||
textStyle: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
onItemSelected: (value) {
|
||||
amountController.text = value;
|
||||
onAmountChanged(value);
|
||||
},
|
||||
caption: '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: Text(S.of(context).choose_card_value + ':',
|
||||
maxLines: 2,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: const SizedBox()),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NumberTextField(
|
||||
controller: quantityController,
|
||||
focusNode: quantityFieldFocus,
|
||||
min: 1,
|
||||
max: 99,
|
||||
onChanged: (value) => onQuantityChanged(value),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: Text(S.of(context).quantity + ':',
|
||||
maxLines: 1,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: const SizedBox()),
|
||||
Expanded(
|
||||
flex: 12,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Observer(
|
||||
builder: (_) => AutoSizeText(
|
||||
'$fiatCurrency ${cakePayBuyCardViewModel.totalAmount}',
|
||||
maxLines: 1,
|
||||
style: textMediumSemiBold(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor)))),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 1.0,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: Text(S.of(context).total + ':',
|
||||
maxLines: 1,
|
||||
style: textSmall(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EnterAmountWidget extends StatelessWidget {
|
||||
const _EnterAmountWidget({
|
||||
required this.minValue,
|
||||
required this.maxValue,
|
||||
required this.fiatCurrency,
|
||||
required this.amountFieldFocus,
|
||||
required this.amountController,
|
||||
required this.onAmountChanged,
|
||||
});
|
||||
|
||||
final String minValue;
|
||||
final String maxValue;
|
||||
final String fiatCurrency;
|
||||
final FocusNode amountFieldFocus;
|
||||
final TextEditingController amountController;
|
||||
final Function(String) onAmountChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: BaseTextFormField(
|
||||
controller: amountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
hintText: '0.00',
|
||||
maxLines: null,
|
||||
borderColor: Colors.transparent,
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Text(
|
||||
'$fiatCurrency: ',
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
textStyle:
|
||||
textMediumSemiBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
placeholderTextStyle: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^\d+(\.|\,)?\d{0,2}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).min_amount(minValue) + ' $fiatCurrency',
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||
Text(S.of(context).max_amount(maxValue) + ' $fiatCurrency',
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor)),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+142
-106
@@ -1,42 +1,40 @@
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.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/screens/cake_pay/widgets/card_item.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/card_menu.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.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:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/debounce.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class IoniaManageCardsPage extends BasePage {
|
||||
IoniaManageCardsPage(this._cardsListViewModel): searchFocusNode = FocusNode() {
|
||||
class CakePayCardsPage extends BasePage {
|
||||
CakePayCardsPage(this._cardsListViewModel) : searchFocusNode = FocusNode() {
|
||||
_searchController.addListener(() {
|
||||
if (_searchController.text != _cardsListViewModel.searchString) {
|
||||
_searchDebounce.run(() {
|
||||
_cardsListViewModel.searchMerchant(_searchController.text);
|
||||
_cardsListViewModel.resetLoadingNextPageState();
|
||||
_cardsListViewModel.getVendors(text: _searchController.text);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_cardsListViewModel.getMerchants();
|
||||
|
||||
}
|
||||
|
||||
final FocusNode searchFocusNode;
|
||||
final IoniaGiftCardsListViewModel _cardsListViewModel;
|
||||
final CakePayCardsListViewModel _cardsListViewModel;
|
||||
|
||||
final _searchDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _searchController = TextEditingController();
|
||||
@@ -46,8 +44,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||
(BuildContext context, Widget scaffold) =>
|
||||
GradientBackground(scaffold: scaffold);
|
||||
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
@@ -58,7 +55,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.of(context).gift_cards,
|
||||
'Cake Pay',
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
@@ -68,9 +65,17 @@ class IoniaManageCardsPage extends BasePage {
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
return _TrailingIcon(
|
||||
asset: 'assets/images/profile.png',
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.ioniaAccountPage),
|
||||
);
|
||||
asset: 'assets/images/profile.png',
|
||||
iconColor: pageIconColor(context) ?? Colors.white,
|
||||
onPressed: () {
|
||||
_cardsListViewModel.isCakePayUserAuthenticated().then((value) {
|
||||
if (value) {
|
||||
Navigator.pushNamed(context, Routes.cakePayAccountPage);
|
||||
return;
|
||||
}
|
||||
Navigator.pushNamed(context, Routes.cakePayWelcomePage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -79,8 +84,12 @@ class IoniaManageCardsPage extends BasePage {
|
||||
label: S.of(context).filter_by,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
await showCategoryFilter(context);
|
||||
_cardsListViewModel.getMerchants();
|
||||
_cardsListViewModel.storeInitialFilterStates();
|
||||
await showFilterWidget(context);
|
||||
if (_cardsListViewModel.hasFiltersChanged) {
|
||||
_cardsListViewModel.resetLoadingNextPageState();
|
||||
_cardsListViewModel.getVendors();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
@@ -120,7 +129,7 @@ class IoniaManageCardsPage extends BasePage {
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: IoniaManageCardsPageBody(
|
||||
child: CakePayCardsPageBody(
|
||||
cardsListViewModel: _cardsListViewModel,
|
||||
),
|
||||
),
|
||||
@@ -129,36 +138,35 @@ class IoniaManageCardsPage extends BasePage {
|
||||
);
|
||||
}
|
||||
|
||||
Future <void> showCategoryFilter(BuildContext context) async {
|
||||
Future<void> showFilterWidget(BuildContext context) async {
|
||||
return showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return IoniaFilterModal(
|
||||
ioniaGiftCardsListViewModel: _cardsListViewModel,
|
||||
);
|
||||
return FilterWidget(filterItems: _cardsListViewModel.createFilterItems);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IoniaManageCardsPageBody extends StatefulWidget {
|
||||
const IoniaManageCardsPageBody({
|
||||
class CakePayCardsPageBody extends StatefulWidget {
|
||||
const CakePayCardsPageBody({
|
||||
Key? key,
|
||||
required this.cardsListViewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
final IoniaGiftCardsListViewModel cardsListViewModel;
|
||||
final CakePayCardsListViewModel cardsListViewModel;
|
||||
|
||||
@override
|
||||
_IoniaManageCardsPageBodyState createState() => _IoniaManageCardsPageBodyState();
|
||||
_CakePayCardsPageBodyState createState() => _CakePayCardsPageBodyState();
|
||||
}
|
||||
|
||||
class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
||||
class _CakePayCardsPageBodyState extends State<CakePayCardsPageBody> {
|
||||
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;
|
||||
bool get isAlwaysShowScrollThumb => merchantsList.isEmpty ? false : merchantsList.length > 3;
|
||||
|
||||
List<CakePayVendor> get merchantsList => widget.cardsListViewModel.cakePayVendors;
|
||||
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@@ -166,61 +174,93 @@ class _IoniaManageCardsPageBodyState extends State<IoniaManageCardsPageBody> {
|
||||
void initState() {
|
||||
_scrollController.addListener(() {
|
||||
final scrollOffsetFromTop = _scrollController.hasClients
|
||||
? (_scrollController.offset / _scrollController.position.maxScrollExtent * (backgroundHeight - thumbHeight))
|
||||
? (_scrollController.offset /
|
||||
_scrollController.position.maxScrollExtent *
|
||||
(backgroundHeight - thumbHeight))
|
||||
: 0.0;
|
||||
widget.cardsListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
|
||||
|
||||
double threshold = 200.0;
|
||||
bool isNearBottom =
|
||||
_scrollController.offset >= _scrollController.position.maxScrollExtent - threshold;
|
||||
if (isNearBottom && !_scrollController.position.outOfRange) {
|
||||
widget.cardsListViewModel.fetchNextPage();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final merchantState = widget.cardsListViewModel.merchantState;
|
||||
if (merchantState is IoniaLoadedMerchantState) {
|
||||
return Observer(builder: (_) {
|
||||
final vendorsState = widget.cardsListViewModel.vendorsState;
|
||||
if (vendorsState is CakePayVendorLoadedState) {
|
||||
bool isLoadingMore = widget.cardsListViewModel.isLoadingNextPage;
|
||||
final vendors = widget.cardsListViewModel.cakePayVendors;
|
||||
|
||||
if (vendors.isEmpty) {
|
||||
return Center(child: Text(S.of(context).no_cards_found));
|
||||
}
|
||||
return Stack(children: [
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
controller: _scrollController,
|
||||
itemCount: merchantsList.length,
|
||||
separatorBuilder: (_, __) => SizedBox(height: 4),
|
||||
itemBuilder: (_, index) {
|
||||
final merchant = merchantsList[index];
|
||||
return CardItem(
|
||||
logoUrl: merchant.logoUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.ioniaBuyGiftCardPage, arguments: [merchant]);
|
||||
},
|
||||
title: merchant.legalName,
|
||||
subTitle: merchant.avaibilityStatus,
|
||||
backgroundColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
discount: merchant.discount,
|
||||
);
|
||||
},
|
||||
),
|
||||
isAlwaysShowScrollThumb
|
||||
? CakeScrollbar(
|
||||
backgroundHeight: backgroundHeight,
|
||||
thumbHeight: thumbHeight,
|
||||
rightOffset: 1,
|
||||
width: 3,
|
||||
backgroundColor: Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.05),
|
||||
thumbColor: Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.5),
|
||||
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
|
||||
)
|
||||
: Offstage()
|
||||
]);
|
||||
}
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
||||
GridView.builder(
|
||||
controller: _scrollController,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: responsiveLayoutUtil.shouldRenderTabletUI ? 2 : 1,
|
||||
childAspectRatio: 5,
|
||||
crossAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||
mainAxisSpacing: responsiveLayoutUtil.shouldRenderTabletUI ? 10 : 5,
|
||||
),
|
||||
padding: EdgeInsets.only(left: 2, right: 22),
|
||||
itemCount: vendors.length + (isLoadingMore ? 1 : 0),
|
||||
itemBuilder: (_, index) {
|
||||
if (index >= vendors.length) {
|
||||
return _VendorLoadedIndicator();
|
||||
}
|
||||
final vendor = vendors[index];
|
||||
return CardItem(
|
||||
logoUrl: vendor.card?.cardImageUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.cakePayBuyCardPage, arguments: [vendor]);
|
||||
},
|
||||
title: vendor.name,
|
||||
subTitle: vendor.card?.description ?? '',
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
titleColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
subtitleColor: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
discount: 0.0,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
isAlwaysShowScrollThumb
|
||||
? CakeScrollbar(
|
||||
backgroundHeight: backgroundHeight,
|
||||
thumbHeight: thumbHeight,
|
||||
rightOffset: 1,
|
||||
width: 3,
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.05),
|
||||
thumbColor:
|
||||
Theme.of(context).extension<FilterTheme>()!.iconColor.withOpacity(0.5),
|
||||
fromTop: widget.cardsListViewModel.scrollOffsetFromTop,
|
||||
)
|
||||
: Offstage()
|
||||
]);
|
||||
}
|
||||
return _VendorLoadedIndicator();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _VendorLoadedIndicator extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -233,6 +273,7 @@ class _SearchWidget extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = ExcludeSemantics(
|
||||
@@ -284,30 +325,25 @@ class _SearchWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _TrailingIcon extends StatelessWidget {
|
||||
const _TrailingIcon({required this.asset, this.onPressed});
|
||||
const _TrailingIcon({required this.asset, this.onPressed, required this.iconColor});
|
||||
|
||||
final String asset;
|
||||
final VoidCallback? onPressed;
|
||||
final Color iconColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Semantics(
|
||||
label: S.of(context).profile,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: BoxConstraints(),
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
iconSize: 25,
|
||||
onPressed: onPressed,
|
||||
icon: Image.asset(
|
||||
asset,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
label: S.of(context).profile,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: BoxConstraints(),
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: onPressed,
|
||||
icon: ImageIcon(AssetImage(asset), size: 25, color: iconColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay_card.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/cake_pay_alert_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/image_placeholder.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/link_extractor.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.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/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/cake_pay/cake_pay_purchase_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class CakePayBuyCardDetailPage extends BasePage {
|
||||
CakePayBuyCardDetailPage(this.cakePayPurchaseViewModel);
|
||||
|
||||
final CakePayPurchaseViewModel cakePayPurchaseViewModel;
|
||||
|
||||
@override
|
||||
String get title => cakePayPurchaseViewModel.card.name;
|
||||
|
||||
@override
|
||||
Widget? middle(BuildContext context) {
|
||||
return Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: titleColor(context)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? trailing(BuildContext context) => null;
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
final card = cakePayPurchaseViewModel.card;
|
||||
|
||||
return ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Observer(builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(height: 36),
|
||||
ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.horizontal(left: Radius.circular(20), right: Radius.circular(20)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.horizontal(
|
||||
left: Radius.circular(20), right: Radius.circular(20)),
|
||||
child: Image.network(
|
||||
card.cardImageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (BuildContext context, Widget child,
|
||||
ImageChunkEvent? loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Center(child: CircularProgressIndicator());
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
CakePayCardImagePlaceholder(),
|
||||
),
|
||||
)),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).value + ':',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'${cakePayPurchaseViewModel.amount.toStringAsFixed(2)} ${cakePayPurchaseViewModel.fiatCurrency}',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).quantity + ':',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'${cakePayPurchaseViewModel.quantity}',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).total + ':',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'${cakePayPurchaseViewModel.totalAmount.toStringAsFixed(2)} ${cakePayPurchaseViewModel.fiatCurrency}',
|
||||
style: textLarge(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: TextIconButton(
|
||||
label: S.of(context).how_to_use_card,
|
||||
onTap: () => _showHowToUseCard(context, card),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
if (card.expiryAndValidity != null && card.expiryAndValidity!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).expiry_and_validity + ':',
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
SizedBox(height: 10),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
card.expiryAndValidity ?? '',
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Observer(builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
isLoading: cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState,
|
||||
onPressed: () => purchaseCard(context),
|
||||
text: S.of(context).purchase_gift_card,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
);
|
||||
}),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => _showTermsAndCondition(context, card.termsAndConditions),
|
||||
child: Text(S.of(context).settings_terms_and_conditions,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).primaryColor,
|
||||
).copyWith(fontSize: 12)),
|
||||
),
|
||||
SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showTermsAndCondition(BuildContext context, String? termsAndConditions) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CakePayAlertModal(
|
||||
title: S.of(context).settings_terms_and_conditions,
|
||||
content: Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: ClickableLinksText(
|
||||
text: termsAndConditions ?? '',
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
actionTitle: S.of(context).agree,
|
||||
showCloseButton: false,
|
||||
heightFactor: 0.6,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> purchaseCard(BuildContext context) async {
|
||||
bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged();
|
||||
if (!isLogged) {
|
||||
Navigator.of(context).pushNamed(Routes.cakePayWelcomePage);
|
||||
} else {
|
||||
await cakePayPurchaseViewModel.createOrder();
|
||||
}
|
||||
}
|
||||
|
||||
void _showHowToUseCard(
|
||||
BuildContext context,
|
||||
CakePayCard card,
|
||||
) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CakePayAlertModal(
|
||||
title: S.of(context).how_to_use_card,
|
||||
content: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text(
|
||||
card.name,
|
||||
style: textLargeSemiBold(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
)),
|
||||
ClickableLinksText(
|
||||
text: card.howToUse ?? '',
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 18,
|
||||
fontStyle: FontStyle.italic,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
]),
|
||||
actionTitle: S.current.got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showConfirmSendingAlert(BuildContext context) async {
|
||||
if (cakePayPurchaseViewModel.order == null) {
|
||||
return;
|
||||
}
|
||||
ReactionDisposer? disposer;
|
||||
|
||||
disposer = reaction((_) => cakePayPurchaseViewModel.isOrderExpired, (bool isExpired) {
|
||||
if (isExpired) {
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
if (disposer != null) {
|
||||
disposer();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final order = cakePayPurchaseViewModel.order;
|
||||
final pendingTransaction = cakePayPurchaseViewModel.sendViewModel.pendingTransaction!;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Observer(
|
||||
builder: (_) => ConfirmSendingAlert(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
paymentId: S.of(context).payment_id,
|
||||
paymentIdValue: order?.orderId,
|
||||
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
|
||||
onDispose: () => _handleDispose(disposer),
|
||||
amount: S.of(context).send_amount,
|
||||
amountValue: pendingTransaction.amountFormatted,
|
||||
fiatAmountValue:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(context).send_fee,
|
||||
feeValue: pendingTransaction.feeFormatted,
|
||||
feeFiatAmount:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
feeRate: pendingTransaction.feeRate,
|
||||
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(context).pop();
|
||||
await cakePayPurchaseViewModel.sendViewModel.commitTransaction();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(context).pop()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => cakePayPurchaseViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showStateAlert(context, S.of(context).error, state.error);
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _showConfirmSendingAlert(context);
|
||||
});
|
||||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
cakePayPurchaseViewModel.sendViewModel.clearOutputs();
|
||||
if (context.mounted) {
|
||||
showStateAlert(context, S.of(context).sending, S.of(context).transaction_sent);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
void showStateAlert(BuildContext context, String title, String content) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: title,
|
||||
alertContent: content,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDispose(ReactionDisposer? disposer) {
|
||||
cakePayPurchaseViewModel.dispose();
|
||||
if (disposer != null) {
|
||||
disposer();
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,8 +5,8 @@ import 'package:cake_wallet/themes/extensions/cake_scrollbar_theme.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IoniaAlertModal extends StatelessWidget {
|
||||
const IoniaAlertModal({
|
||||
class CakePayAlertModal extends StatelessWidget {
|
||||
const CakePayAlertModal({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.content,
|
||||
+2
-2
@@ -3,8 +3,8 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class IoniaTile extends StatelessWidget {
|
||||
const IoniaTile({
|
||||
class CakePayTile extends StatelessWidget {
|
||||
const CakePayTile({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'image_placeholder.dart';
|
||||
|
||||
class CardItem extends StatelessWidget {
|
||||
CardItem({
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
required this.backgroundColor,
|
||||
required this.titleColor,
|
||||
required this.subtitleColor,
|
||||
this.hideBorder = false,
|
||||
this.discount = 0.0,
|
||||
this.isAmount = false,
|
||||
this.discountBackground,
|
||||
this.onTap,
|
||||
this.logoUrl,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final String? logoUrl;
|
||||
final double discount;
|
||||
final bool isAmount;
|
||||
final bool hideBorder;
|
||||
final Color backgroundColor;
|
||||
final Color titleColor;
|
||||
final Color subtitleColor;
|
||||
final AssetImage? discountBackground;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Theme(
|
||||
data: ThemeData(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: hideBorder
|
||||
? Border.all(color: Colors.transparent)
|
||||
: Border.all(color: Colors.white.withOpacity(0.20)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (logoUrl != null)
|
||||
AspectRatio(
|
||||
aspectRatio: 1.8,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
child: Image.network(
|
||||
logoUrl!,
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder:
|
||||
(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Center(child: CircularProgressIndicator());
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) => CakePayCardImagePlaceholder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: titleColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subTitle,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: titleColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CakePayCardImagePlaceholder extends StatelessWidget {
|
||||
const CakePayCardImagePlaceholder({this.text});
|
||||
|
||||
final String? text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.8,
|
||||
child: Container(
|
||||
child: Center(
|
||||
child: Text(
|
||||
text ?? 'Image not found!',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ClickableLinksText extends StatelessWidget {
|
||||
const ClickableLinksText({
|
||||
required this.text,
|
||||
required this.textStyle,
|
||||
this.linkStyle,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final TextStyle textStyle;
|
||||
final TextStyle? linkStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<InlineSpan> spans = [];
|
||||
RegExp linkRegExp = RegExp(r'(https?://[^\s]+)');
|
||||
Iterable<Match> matches = linkRegExp.allMatches(text);
|
||||
|
||||
int previousEnd = 0;
|
||||
matches.forEach((match) {
|
||||
if (match.start > previousEnd) {
|
||||
spans.add(TextSpan(text: text.substring(previousEnd, match.start), style: textStyle));
|
||||
}
|
||||
String url = text.substring(match.start, match.end);
|
||||
if (url.toLowerCase().endsWith('.md')) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: url,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: url,
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(Uri.parse(url));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
previousEnd = match.end;
|
||||
});
|
||||
|
||||
if (previousEnd < text.length) {
|
||||
spans.add(TextSpan(text: text.substring(previousEnd), style: textStyle));
|
||||
}
|
||||
|
||||
return RichText(text: TextSpan(children: spans));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart';
|
||||
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
@@ -351,10 +352,12 @@ class _DashboardPageView extends BasePage {
|
||||
|
||||
_showVulnerableSeedsPopup(context);
|
||||
|
||||
_showHavenPopup(context);
|
||||
|
||||
var needToPresentYat = false;
|
||||
var isInactive = false;
|
||||
|
||||
_onInactiveSub = rootKey.currentState!.isInactive.listen(
|
||||
_onInactiveSub = rootKey.currentState?.isInactive.listen(
|
||||
(inactive) {
|
||||
isInactive = inactive;
|
||||
|
||||
@@ -428,4 +431,22 @@ class _DashboardPageView extends BasePage {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showHavenPopup(BuildContext context) async {
|
||||
final List<String> havenWalletList = await dashboardViewModel.checkForHavenWallets();
|
||||
|
||||
if (havenWalletList.isNotEmpty) {
|
||||
Future<void>.delayed(
|
||||
Duration(seconds: 1),
|
||||
() {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return HavenWalletRemovalPopup(havenWalletList);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
|
||||
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
|
||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
@@ -171,8 +169,7 @@ class AddressPage extends BasePage {
|
||||
textSize: 14,
|
||||
height: 50,
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
@@ -204,8 +201,12 @@ class AddressPage extends BasePage {
|
||||
final sharedPreferences = getIt.get<SharedPreferences>();
|
||||
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
|
||||
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
|
||||
final donationWalletName =
|
||||
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
|
||||
|
||||
if (clearnetUrl != null && onionUrl != null) {
|
||||
if (clearnetUrl != null &&
|
||||
onionUrl != null &&
|
||||
addressListViewModel.wallet.name == donationWalletName) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.anonPayReceivePage,
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.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:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class CakeFeaturesPage extends StatelessWidget {
|
||||
CakeFeaturesPage({
|
||||
required this.dashboardViewModel,
|
||||
required this.cakeFeaturesViewModel,
|
||||
});
|
||||
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||
@@ -45,20 +46,11 @@ class CakeFeaturesPage extends StatelessWidget {
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: <Widget>[
|
||||
// SizedBox(height: 20),
|
||||
// DashBoardRoundedCardWidget(
|
||||
// onTap: () => launchUrl(
|
||||
// Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"),
|
||||
// mode: LaunchMode.externalApplication,
|
||||
// ),
|
||||
// title: S.of(context).cake_pay_title,
|
||||
// subTitle: S.of(context).cake_pay_subtitle,
|
||||
// ),
|
||||
SizedBox(height: 20),
|
||||
DashBoardRoundedCardWidget(
|
||||
onTap: () => _launchUrl("buy.cakepay.com"),
|
||||
title: S.of(context).cake_pay_web_cards_title,
|
||||
subTitle: S.of(context).cake_pay_web_cards_subtitle,
|
||||
onTap: () => _navigatorToGiftCardsPage(context),
|
||||
title: 'Cake Pay',
|
||||
subTitle: S.of(context).cake_pay_subtitle,
|
||||
svgPicture: SvgPicture.asset(
|
||||
'assets/images/cards.svg',
|
||||
height: 125,
|
||||
@@ -88,6 +80,28 @@ class CakeFeaturesPage extends StatelessWidget {
|
||||
Uri.https(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
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.pushNamed(context, Routes.cakePayCardsPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.d
|
||||
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -82,6 +83,10 @@ class TransactionsPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
final transaction = item.transaction;
|
||||
final transactionType = dashboardViewModel.type == WalletType.ethereum &&
|
||||
transaction.evmSignatureName == 'approval'
|
||||
? ' (${transaction.evmSignatureName})'
|
||||
: '';
|
||||
|
||||
return Observer(
|
||||
builder: (_) => TransactionRow(
|
||||
@@ -95,7 +100,8 @@ class TransactionsPage extends StatelessWidget {
|
||||
? ''
|
||||
: item.formattedFiatAmount,
|
||||
isPending: transaction.isPending,
|
||||
title: item.formattedTitle + item.formattedStatus,
|
||||
title: item.formattedTitle +
|
||||
item.formattedStatus + ' $transactionType',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,21 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class FilterWidget extends StatelessWidget {
|
||||
FilterWidget({required this.dashboardViewModel});
|
||||
FilterWidget({required this.filterItems});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -27,75 +30,90 @@ class FilterWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
child: Container(
|
||||
color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
S.of(context).filter_by,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
S.of(context).filter_by,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionDivider,
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: filterItems.length,
|
||||
separatorBuilder: (context, _) => sectionDivider,
|
||||
itemBuilder: (_, index1) {
|
||||
final title = filterItems.keys.elementAt(index1);
|
||||
final section = filterItems.values.elementAt(index1);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, left: 24, right: 24),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
sectionDivider,
|
||||
ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: dashboardViewModel.filterItems.length,
|
||||
separatorBuilder: (context, _) => sectionDivider,
|
||||
itemBuilder: (_, index1) {
|
||||
final title = dashboardViewModel.filterItems.keys
|
||||
.elementAt(index1);
|
||||
final section = dashboardViewModel.filterItems.values
|
||||
.elementAt(index1);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: 20, left: 24, right: 24),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: section.length,
|
||||
itemBuilder: (_, index2) {
|
||||
final item = section[index2];
|
||||
final content = Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: item.value(),
|
||||
caption: item.caption,
|
||||
gradientBackground: true,
|
||||
borderColor:
|
||||
Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) =>
|
||||
item.onChanged(),
|
||||
));
|
||||
return FilterTile(child: content);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 28.0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: section.length,
|
||||
itemBuilder: (_, index2) {
|
||||
final item = section[index2];
|
||||
|
||||
if (item is DropdownFilterItem) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
),
|
||||
child: DropdownFilterList(
|
||||
items: item.items,
|
||||
caption: item.caption,
|
||||
selectedItem: item.selectedItem,
|
||||
onItemSelected: item.onItemSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final content = Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: item.value(),
|
||||
caption: item.caption,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) => item.onChanged(),
|
||||
));
|
||||
return FilterTile(child: content);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ class HeaderRow extends StatelessWidget {
|
||||
onTap: () {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel),
|
||||
builder: (context) => FilterWidget(filterItems: dashboardViewModel.filterItems),
|
||||
);
|
||||
},
|
||||
child: Semantics(
|
||||
|
||||
@@ -36,7 +36,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
|
||||
this.polygonIcon = Image.asset('assets/images/matic_icon.png'),
|
||||
this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
|
||||
this.tronIcon = Image.asset('assets/images/trx_icon.png');
|
||||
this.tronIcon = Image.asset('assets/images/trx_icon.png'),
|
||||
this.wowneroIcon = Image.asset('assets/images/wownero_icon.png');
|
||||
|
||||
final largeScreen = 731;
|
||||
|
||||
@@ -60,6 +61,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
Image polygonIcon;
|
||||
Image solanaIcon;
|
||||
Image tronIcon;
|
||||
Image wowneroIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -236,6 +238,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
case WalletType.wownero:
|
||||
return wowneroIcon;
|
||||
default:
|
||||
throw Exception('No icon for ${type.toString()}');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/rounded_checkbox.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
||||
@@ -99,6 +99,14 @@ class ExchangePage extends BasePage {
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/primary_button.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class IoniaWelcomePage extends BasePage {
|
||||
IoniaWelcomePage();
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) {
|
||||
return Text(
|
||||
S.current.welcome_to_cakepay,
|
||||
style: textMediumSemiBold(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 90),
|
||||
Text(
|
||||
S.of(context).about_cake_pay,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
S.of(context).cake_pay_account_note,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
PrimaryButton(
|
||||
text: S.of(context).create_account,
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.ioniaCreateAccountPage),
|
||||
color: Theme.of(context).primaryColor,
|
||||
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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
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)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_create_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/card_item.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/order_theme.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';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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 {
|
||||
_IoniaCardTabsState();
|
||||
|
||||
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).extension<CakeTextTheme>()!.titleColor
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(
|
||||
25.0,
|
||||
),
|
||||
),
|
||||
child: Theme(
|
||||
data: ThemeData(primaryTextTheme: TextTheme(bodyLarge: TextStyle(backgroundColor: Colors.transparent))),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
indicator: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
25.0,
|
||||
),
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
labelColor: Theme.of(context).extension<OrderTheme>()!.iconColor,
|
||||
unselectedLabelColor:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
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,
|
||||
isLoading: viewModel.merchantState is IoniaLoadingMerchantState,
|
||||
onTap: (giftCard) {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.ioniaGiftCardDetailPage,
|
||||
arguments: [giftCard])
|
||||
.then((_) => viewModel.updateUserGiftCards());
|
||||
}),
|
||||
_IoniaCardListView(
|
||||
emptyText: S.of(context).gift_card_redeemed_note,
|
||||
merchList: viewModel.redeemedMerchs,
|
||||
isLoading: viewModel.merchantState is IoniaLoadingMerchantState,
|
||||
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,
|
||||
this.isLoading = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final String emptyText;
|
||||
final List<IoniaGiftCard> merchList;
|
||||
final void Function(IoniaGiftCard giftCard) onTap;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(isLoading){
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
return merchList.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
emptyText,
|
||||
textAlign: TextAlign.center,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: 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).extension<CakeTextTheme>()!.titleColor
|
||||
.withOpacity(0.1),
|
||||
discount: 0,
|
||||
hideBorder: true,
|
||||
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
|
||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: '',
|
||||
logoUrl: merchant.logoUrl,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/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';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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).primaryColor,
|
||||
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,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget content;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: content,
|
||||
padding: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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).primaryColor,
|
||||
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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,478 +0,0 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/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/themes/extensions/exchange_page_theme.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';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor),
|
||||
);
|
||||
}
|
||||
|
||||
@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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
],
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if(ioniaPurchaseViewModel.ioniaMerchant.acceptsTips)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24.0, 24.0, 0, 24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).tip,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Observer(
|
||||
builder: (_) => TipButtonGroup(
|
||||
selectedTip: ioniaPurchaseViewModel.selectedTip!.percentage,
|
||||
tipsList: ioniaPurchaseViewModel.tips,
|
||||
onSelect: (value) => ioniaPurchaseViewModel.addTip(value),
|
||||
amount: ioniaPurchaseViewModel.amount,
|
||||
merchant: ioniaPurchaseViewModel.ioniaMerchant,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
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).primaryColor,
|
||||
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).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor,
|
||||
).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).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
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).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
instruction.body,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
)
|
||||
];
|
||||
})
|
||||
.expand((e) => e)
|
||||
.toList()),
|
||||
actionTitle: S.current.got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _presentSuccessfulInvoiceCreationPopup(BuildContext context) async {
|
||||
if (ioniaPurchaseViewModel.invoice == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final amount = ioniaPurchaseViewModel.invoice!.totalAmount;
|
||||
final addresses = ioniaPurchaseViewModel.invoice!.outAddresses;
|
||||
ioniaPurchaseViewModel.sendViewModel.outputs.first.setCryptoAmount(amount);
|
||||
ioniaPurchaseViewModel.sendViewModel.outputs.first.address = addresses.first;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return ConfirmSendingAlert(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
paymentId: S.of(context).payment_id,
|
||||
paymentIdValue: ioniaPurchaseViewModel.invoice!.paymentId,
|
||||
amount: S.of(context).send_amount,
|
||||
amountValue: '$amount ${ioniaPurchaseViewModel.invoice!.chain}',
|
||||
fiatAmountValue:
|
||||
'~ ${ioniaPurchaseViewModel.sendViewModel.outputs.first.fiatAmount} '
|
||||
'${ioniaPurchaseViewModel.sendViewModel.fiat.title}',
|
||||
fee: S.of(context).send_fee,
|
||||
feeValue:
|
||||
'${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFee} '
|
||||
'${ioniaPurchaseViewModel.invoice!.chain}',
|
||||
feeFiatAmount:
|
||||
'${ioniaPurchaseViewModel.sendViewModel.outputs.first.estimatedFeeFiatAmount} '
|
||||
'${ioniaPurchaseViewModel.sendViewModel.fiat.title}',
|
||||
outputs: ioniaPurchaseViewModel.sendViewModel.outputs,
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
alertLeftActionButtonTextColor: Colors.white,
|
||||
alertRightActionButtonTextColor: Colors.white,
|
||||
alertLeftActionButtonColor: Palette.brightOrange,
|
||||
alertRightActionButtonColor: Theme.of(context).primaryColor,
|
||||
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,
|
||||
required this.amount,
|
||||
required this.merchant,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(IoniaTip) onSelect;
|
||||
final double selectedTip;
|
||||
final List<IoniaTip> tipsList;
|
||||
final double amount;
|
||||
final IoniaMerchant merchant;
|
||||
|
||||
bool _isSelected(double value) => selectedTip == value;
|
||||
Set<double> get filter => tipsList.map((e) => e.percentage).toSet();
|
||||
bool get _isCustomSelected => !filter.contains(selectedTip);
|
||||
|
||||
@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: tip.isCustom ? _isCustomSelected : _isSelected(tip.percentage),
|
||||
onTap: () async {
|
||||
IoniaTip ioniaTip = tip;
|
||||
if(tip.isCustom){
|
||||
final customTip = await Navigator.pushNamed(context, Routes.ioniaCustomTipPage, arguments: [amount, merchant, tip]) as IoniaTip?;
|
||||
ioniaTip = customTip ?? tip;
|
||||
}
|
||||
onSelect(ioniaTip);
|
||||
},
|
||||
caption: tip.isCustom ? S.of(context).custom : '${tip.percentage.toStringAsFixed(0)}%',
|
||||
subTitle: tip.isCustom ? null : '\$${tip.additionalAmount.toStringAsFixed(2)}',
|
||||
));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class TipButton extends StatelessWidget {
|
||||
const TipButton({
|
||||
required this.caption,
|
||||
required this.onTap,
|
||||
this.subTitle,
|
||||
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).extension<CakeTextTheme>()!.titleColor;
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? Theme.of(context).dialogTheme.backgroundColor!
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor;
|
||||
}
|
||||
|
||||
Color subTitleTextColor(BuildContext context) {
|
||||
if (isDark(context)) {
|
||||
return Theme.of(context).extension<CakeTextTheme>()!.titleColor;
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? Theme.of(context).dialogTheme.backgroundColor!
|
||||
: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor;
|
||||
}
|
||||
|
||||
Color? backgroundColor(BuildContext context) {
|
||||
if (isDark(context)) {
|
||||
return isSelected
|
||||
? null
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.01);
|
||||
}
|
||||
|
||||
return isSelected
|
||||
? null
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor.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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_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';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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
|
||||
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 merchant = ioniaBuyCardViewModel.ioniaMerchant;
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 150),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: BaseTextFormField(
|
||||
controller: _amountController,
|
||||
focusNode: _amountFieldFocus,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')),
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^\d+(\.|\,)?\d{0,2}'),
|
||||
),
|
||||
],
|
||||
hintText: '1000',
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 36,
|
||||
),
|
||||
prefixIcon: Text(
|
||||
'USD: ',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 36,
|
||||
),
|
||||
),
|
||||
textColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
height: 1,
|
||||
),
|
||||
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).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
S.of(context).max_amount(merchant.maximumCardPurchase.toStringAsFixed(2)),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
title: merchant.legalName,
|
||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
.withOpacity(0.1),
|
||||
discount: merchant.discount,
|
||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: merchant.avaibilityStatus,
|
||||
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).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/core/execution_state.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_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';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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
|
||||
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).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
], 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).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 36,
|
||||
),
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
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.formattedRemaining} ${S.of(context).remaining}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
title: giftCard.legalName,
|
||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
.withOpacity(0.1),
|
||||
discount: giftCard.remainingAmount,
|
||||
isAmount: true,
|
||||
discountBackground: AssetImage('assets/images/red_badge_discount.png'),
|
||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
subtitleColor: Theme.of(context).hintColor,
|
||||
subTitle: S.of(context).online,
|
||||
logoUrl: giftCard.logoUrl,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: LoadingPrimaryButton(
|
||||
isLoading: ioniaCustomRedeemViewModel.redeemState is IsExecutingState,
|
||||
isDisabled: ioniaCustomRedeemViewModel.disableRedeem,
|
||||
text: S.of(context).add_custom_redemption,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
onPressed: () => ioniaCustomRedeemViewModel.addCustomRedeem().then((value) {
|
||||
Navigator.of(context).pop(ioniaCustomRedeemViewModel.remaining.toString());
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
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_custom_tip_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';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
|
||||
class IoniaCustomTipPage extends BasePage {
|
||||
IoniaCustomTipPage(
|
||||
this.customTipViewModel,
|
||||
) : _amountFieldFocus = FocusNode(),
|
||||
_amountController = TextEditingController() {
|
||||
_amountController.addListener(() {
|
||||
customTipViewModel.onTipChanged(_amountController.text);
|
||||
});
|
||||
}
|
||||
|
||||
final IoniaCustomTipViewModel customTipViewModel;
|
||||
|
||||
|
||||
@override
|
||||
String get title => S.current.enter_amount;
|
||||
|
||||
@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 = customTipViewModel.ioniaMerchant;
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFieldFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
], 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).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 36,
|
||||
),
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
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 (customTipViewModel.percentage == 0.0) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
return RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: '\$${_amountController.text}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: ' ${S.of(context).is_percentage} '),
|
||||
TextSpan(text: '${customTipViewModel.percentage.toStringAsFixed(2)}%'),
|
||||
TextSpan(text: ' ${S.of(context).percentageOf(customTipViewModel.amount.toStringAsFixed(2))} '),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: CardItem(
|
||||
title: merchant.legalName,
|
||||
backgroundColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
.withOpacity(0.1),
|
||||
discount: 0.0,
|
||||
titleColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
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(customTipViewModel.customTip);
|
||||
},
|
||||
text: S.of(context).add_tip,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
import 'package:cake_wallet/ionia/ionia_create_state.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/themes/extensions/cake_scrollbar_theme.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';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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).extension<ReceivePageTheme>()!.iconsColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
PrimaryButton(
|
||||
text: S.of(context).order_physical_card,
|
||||
onPressed: () {},
|
||||
color: Color(0xffE9F2FC),
|
||||
textColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
PrimaryButton(
|
||||
text: S.of(context).add_value,
|
||||
onPressed: () {},
|
||||
color: Theme.of(context).primaryColor,
|
||||
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).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: S.of(context).digital_and_physical_card,
|
||||
style: textMediumBold(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
),
|
||||
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).primaryColor,
|
||||
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).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).how_to_use_card,
|
||||
style: textLargeSemiBold(
|
||||
color:
|
||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
S.of(context).signup_for_card_accept_terms,
|
||||
style: textSmallSemiBold(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
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).got_it,
|
||||
color: Color.fromRGBO(233, 242, 252, 1),
|
||||
textColor:
|
||||
Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
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 {
|
||||
const _IoniaDebitCard({
|
||||
Key? key,
|
||||
this.cardInfo,
|
||||
this.isCardSample = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final bool isCardSample;
|
||||
final IoniaVirtualCard? cardInfo;
|
||||
|
||||
@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).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
],
|
||||
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 {
|
||||
const _TitleSubtitleTile({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textSmallSemiBold(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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_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/utils/route_aware.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
|
||||
import 'package:device_display_brightness/device_display_brightness.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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).extension<CakeTextTheme>()!.titleColor,
|
||||
size: 16,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: TextButton(
|
||||
// FIX-ME: Style
|
||||
//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).extension<CakeTextTheme>()!.titleColor),
|
||||
);
|
||||
}
|
||||
|
||||
@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 RouteAwareWidget(
|
||||
pushToWidget: ()=> viewModel.increaseBrightness(),
|
||||
pushToNextWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
|
||||
popNextWidget: ()=> viewModel.increaseBrightness(),
|
||||
popWidget: ()=> DeviceDisplayBrightness.setBrightness(viewModel.brightness),
|
||||
child: 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) ...[
|
||||
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.remainingAmount.toStringAsFixed(2),
|
||||
)),
|
||||
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 Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
onPressed: () async {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.ioniaMoreOptionsPage,
|
||||
arguments: [viewModel.giftCard]) as String?;
|
||||
viewModel.refeshCard();
|
||||
},
|
||||
text: S.of(context).more_options,
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
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).primaryColor,
|
||||
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).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
)),
|
||||
Text(
|
||||
instruction.body,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
),
|
||||
)
|
||||
];
|
||||
})
|
||||
.expand((e) => e)
|
||||
.toList()),
|
||||
actionTitle: S.of(context).got_it,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/themes/extensions/dashboard_page_theme.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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
final amount = await Navigator.of(context)
|
||||
.pushNamed(Routes.ioniaCustomRedeemPage, arguments: [giftCard]) as String?;
|
||||
if (amount != null && amount.isNotEmpty) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
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}) : super(key: key);
|
||||
|
||||
final Widget content;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: content,
|
||||
padding: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.secondGradientBackgroundColor,
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.firstGradientBackgroundColor,
|
||||
],
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.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).extension<CakeTextTheme>()!.titleColor));
|
||||
}
|
||||
|
||||
@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).extension<CakeTextTheme>()!.titleColor))
|
||||
]),
|
||||
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)),
|
||||
if (widget.viewModel.paymentInfo.ioniaOrder.id != null)
|
||||
...[Divider(height: 30),
|
||||
buildDescriptionTileWithCopy(context, S.of(context).order_id, widget.viewModel.paymentInfo.ioniaOrder.id)],
|
||||
if (widget.viewModel.paymentInfo.ioniaOrder.paymentId != null)
|
||||
...[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).extension<CakeTextTheme>()!.titleColor))
|
||||
]));
|
||||
}
|
||||
|
||||
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).extension<CakeTextTheme>()!.titleColor))]);
|
||||
}),
|
||||
],
|
||||
),
|
||||
bottomSection: Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Column(children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
|
||||
child: Text(
|
||||
widget.viewModel.payingByBitcoin ? S.of(context).bitcoin_payments_require_1_confirmation
|
||||
: S.of(context).proceed_after_one_minute,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
).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).primaryColor,
|
||||
textColor: Colors.white);
|
||||
}
|
||||
|
||||
return PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.support),
|
||||
text: S.of(context).contact_support,
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||
})
|
||||
])
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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';
|
||||
@@ -1,144 +0,0 @@
|
||||
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.hideBorder = false,
|
||||
this.discount = 0.0,
|
||||
this.isAmount = false,
|
||||
this.discountBackground,
|
||||
this.onTap,
|
||||
this.logoUrl,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final String? logoUrl;
|
||||
final double discount;
|
||||
final bool isAmount;
|
||||
final bool hideBorder;
|
||||
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: hideBorder ? Border.symmetric(horizontal: BorderSide.none, vertical: BorderSide.none) : 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,
|
||||
isAmount: isAmount,
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.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:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
|
||||
class IoniaFilterModal extends StatelessWidget {
|
||||
IoniaFilterModal({required this.ioniaGiftCardsListViewModel}){
|
||||
ioniaGiftCardsListViewModel.resetIoniaCategories();
|
||||
}
|
||||
|
||||
final IoniaGiftCardsListViewModel ioniaGiftCardsListViewModel;
|
||||
|
||||
@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).primaryColor,
|
||||
),
|
||||
);
|
||||
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).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: TextField(
|
||||
onChanged: ioniaGiftCardsListViewModel.onSearchFilter,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
prefixIcon: searchIcon,
|
||||
hintText: S.of(context).search_category,
|
||||
contentPadding: EdgeInsets.only(bottom: 5),
|
||||
fillColor: Theme.of(context).extension<CakeMenuTheme>()!.dividerColor.withOpacity(0.5),
|
||||
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: ioniaGiftCardsListViewModel.ioniaCategories.length,
|
||||
itemBuilder: (_, index) {
|
||||
final category = ioniaGiftCardsListViewModel.ioniaCategories[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: InkWell(
|
||||
onTap: () => ioniaGiftCardsListViewModel.setSelectedFilter(category),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
category.iconPath,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(category.title,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
).copyWith(fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
final value = ioniaGiftCardsListViewModel.selectedIndices;
|
||||
return RoundedCheckbox(
|
||||
value: value.contains(category),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(bottom: 40),
|
||||
child: CircleAvatar(
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: Palette.darkBlueCraiola,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,14 @@ class NewWalletPage extends BasePage {
|
||||
@override
|
||||
String get title => S.current.new_wallet;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletNameForm(
|
||||
_walletNewVM,
|
||||
@@ -74,7 +82,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
||||
void initState() {
|
||||
_stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async {
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
Navigator.of(navigatorKey.currentContext!)
|
||||
Navigator.of(navigatorKey.currentContext ?? context)
|
||||
.pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ class NewWalletTypePage extends BasePage {
|
||||
String get title =>
|
||||
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletTypeForm(
|
||||
onTypeSelected: onTypeSelected,
|
||||
|
||||
@@ -60,8 +60,7 @@ class AnonPayInvoicePage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
|
||||
receiveOptionViewModel: receiveOptionViewModel,
|
||||
color: titleColor(context));
|
||||
receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context));
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) => TrailButton(
|
||||
@@ -87,30 +86,36 @@ class AnonPayInvoicePage extends BasePage {
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFocusNode,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: Container(
|
||||
decoration: responsiveLayoutUtil.shouldRenderMobileUI ? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientTopPanelColor,
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.secondGradientTopPanelColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFocusNode,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
) : null,
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: Container(
|
||||
decoration: responsiveLayoutUtil.shouldRenderMobileUI
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.extension<ExchangePageTheme>()!
|
||||
.firstGradientTopPanelColor,
|
||||
Theme.of(context)
|
||||
.extension<ExchangePageTheme>()!
|
||||
.secondGradientTopPanelColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Observer(builder: (_) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
|
||||
@@ -143,9 +148,11 @@ class AnonPayInvoicePage extends BasePage {
|
||||
: S.of(context).anonpay_description("a donation link", "donate"),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<ExchangePageTheme>()!.receiveAmountColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12),
|
||||
color: Theme.of(context)
|
||||
.extension<ExchangePageTheme>()!
|
||||
.receiveAmountColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -172,7 +179,7 @@ class AnonPayInvoicePage extends BasePage {
|
||||
anonInvoicePageViewModel.generateDonationLink();
|
||||
}
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
|
||||
),
|
||||
@@ -199,8 +206,12 @@ class AnonPayInvoicePage extends BasePage {
|
||||
final sharedPreferences = getIt.get<SharedPreferences>();
|
||||
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
|
||||
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
|
||||
final donationWalletName =
|
||||
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
|
||||
|
||||
if (clearnetUrl != null && onionUrl != null) {
|
||||
if (clearnetUrl != null &&
|
||||
onionUrl != null &&
|
||||
anonInvoicePageViewModel.currentWalletName == donationWalletName) {
|
||||
Navigator.pushReplacementNamed(context, Routes.anonPayReceivePage,
|
||||
arguments: AnonpayDonationLinkInfo(
|
||||
clearnetUrl: clearnetUrl,
|
||||
|
||||
@@ -33,6 +33,7 @@ class RescanPage extends BasePage {
|
||||
doSingleScan: _rescanViewModel.doSingleScan,
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
walletType: _rescanViewModel.wallet.type,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
|
||||
@@ -21,6 +21,14 @@ class RestoreFromBackupPage extends BasePage {
|
||||
@override
|
||||
String get title => S.current.restore_title_from_backup;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => restoreFromBackupViewModel.state, (ExecutionState state) {
|
||||
|
||||
@@ -171,6 +171,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
|
||||
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
|
||||
onHeightChange: (_) => null,
|
||||
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
||||
walletType: widget.walletRestoreViewModel.type,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -83,12 +83,17 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
}
|
||||
|
||||
void onSeedChange(String seed) {
|
||||
if (widget.type == WalletType.monero && Polyseed.isValidSeed(seed)) {
|
||||
if ((widget.type == WalletType.monero || widget.type == WalletType.wownero) &&
|
||||
Polyseed.isValidSeed(seed)) {
|
||||
final lang = PolyseedLang.getByPhrase(seed);
|
||||
|
||||
_changeSeedType(SeedType.polyseed);
|
||||
_changeLanguage(lang.nameEnglish);
|
||||
}
|
||||
if (widget.type == WalletType.wownero && seed.split(" ").length == 14) {
|
||||
_changeSeedType(SeedType.wowneroSeed);
|
||||
_changeLanguage("English");
|
||||
}
|
||||
widget.onSeedChange?.call(seed);
|
||||
}
|
||||
|
||||
@@ -142,14 +147,18 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
language: language,
|
||||
type: widget.type,
|
||||
onSeedChange: onSeedChange),
|
||||
if (widget.type == WalletType.monero)
|
||||
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: SeedType.all,
|
||||
selectedAtIndex: isPolyseed ? 1 : 0,
|
||||
items: _getItems(),
|
||||
selectedAtIndex: isPolyseed
|
||||
? 1
|
||||
: seedTypeController.value.text.contains("14")
|
||||
? 2
|
||||
: 0,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
onItemSelected: _changeSeedType,
|
||||
isSeparated: false,
|
||||
@@ -168,7 +177,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.displayLanguageSelector)
|
||||
if (!seedTypeController.value.text.contains("14") && widget.displayLanguageSelector)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await showPopUp<void>(
|
||||
@@ -192,12 +201,14 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isPolyseed && widget.displayBlockHeightSelector)
|
||||
if ((!isPolyseed) && widget.displayBlockHeightSelector)
|
||||
BlockchainHeightWidget(
|
||||
focusNode: widget.blockHeightFocusNode,
|
||||
key: blockchainHeightKey,
|
||||
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
||||
hasDatePicker: widget.type == WalletType.monero),
|
||||
focusNode: widget.blockHeightFocusNode,
|
||||
key: blockchainHeightKey,
|
||||
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
||||
hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
|
||||
walletType: widget.type,
|
||||
),
|
||||
if (widget.displayPassphrase) ...[
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
@@ -209,7 +220,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
]));
|
||||
}
|
||||
|
||||
bool get isPolyseed => widget.seedTypeViewModel.moneroSeedType == SeedType.polyseed;
|
||||
bool get isPolyseed =>
|
||||
widget.seedTypeViewModel.moneroSeedType == SeedType.polyseed &&
|
||||
(widget.type == WalletType.monero || widget.type == WalletType.wownero);
|
||||
|
||||
Widget get expandIcon => Container(
|
||||
padding: EdgeInsets.all(18),
|
||||
@@ -223,7 +236,11 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
);
|
||||
|
||||
void _changeLanguage(String language) {
|
||||
final setLang = isPolyseed ? "POLYSEED_$language" : language;
|
||||
final setLang = isPolyseed
|
||||
? "POLYSEED_$language"
|
||||
: seedTypeController.value.text.contains("14")
|
||||
? "WOWSEED_" + language
|
||||
: language;
|
||||
setState(() {
|
||||
this.language = setLang;
|
||||
seedWidgetStateKey.currentState!.changeSeedLanguage(setLang);
|
||||
@@ -244,4 +261,15 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||
void _setSeedType(SeedType item) {
|
||||
seedTypeController.text = item.toString();
|
||||
}
|
||||
|
||||
List<SeedType> _getItems() {
|
||||
switch (widget.type) {
|
||||
case WalletType.monero:
|
||||
return [SeedType.legacy, SeedType.polyseed];
|
||||
case WalletType.wownero:
|
||||
return [SeedType.legacy, SeedType.polyseed, SeedType.wowneroSeed];
|
||||
default:
|
||||
return [SeedType.legacy];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
|
||||
class WalletRestorePage extends BasePage {
|
||||
@@ -48,8 +47,7 @@ class WalletRestorePage extends BasePage {
|
||||
}
|
||||
},
|
||||
onSeedChange: (String seed) {
|
||||
final isPolyseed =
|
||||
walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seed);
|
||||
final isPolyseed = walletRestoreViewModel.isPolyseed(seed);
|
||||
_validateOnChange(isPolyseed: isPolyseed);
|
||||
},
|
||||
onLanguageChange: (String language) {
|
||||
@@ -101,6 +99,14 @@ class WalletRestorePage extends BasePage {
|
||||
// String? derivationPath = null;
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => walletRestoreViewModel.state, (ExecutionState state) {
|
||||
@@ -245,12 +251,14 @@ class WalletRestorePage extends BasePage {
|
||||
bool _isValidSeed() {
|
||||
final seedPhrase =
|
||||
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
|
||||
if (walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seedPhrase))
|
||||
return true;
|
||||
if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true;
|
||||
|
||||
final seedWords = seedPhrase.split(' ');
|
||||
|
||||
if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
|
||||
|
||||
if ((walletRestoreViewModel.type == WalletType.monero ||
|
||||
walletRestoreViewModel.type == WalletType.wownero ||
|
||||
walletRestoreViewModel.type == WalletType.haven) &&
|
||||
seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) {
|
||||
return false;
|
||||
|
||||
@@ -136,9 +136,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
widget.authService.requireAuth().then((value) {
|
||||
setState(() {
|
||||
_requestAuth = value;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_requestAuth = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -66,6 +66,14 @@ class SendPage extends BasePage {
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
|
||||
@@ -32,6 +32,14 @@ class SendTemplatePage extends BasePage {
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget trailing(context) => Observer(builder: (_) {
|
||||
return sendTemplateViewModel.recipients.length > 1
|
||||
|
||||
@@ -12,6 +12,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
{required this.alertTitle,
|
||||
this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
@@ -28,11 +29,13 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
this.alertLeftActionButtonTextColor,
|
||||
this.alertRightActionButtonTextColor,
|
||||
this.alertLeftActionButtonColor,
|
||||
this.alertRightActionButtonColor});
|
||||
this.alertRightActionButtonColor,
|
||||
this.onDispose});
|
||||
|
||||
final String alertTitle;
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
@@ -50,6 +53,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
final Color? alertRightActionButtonTextColor;
|
||||
final Color? alertLeftActionButtonColor;
|
||||
final Color? alertRightActionButtonColor;
|
||||
final Function? onDispose;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
@@ -88,6 +92,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
||||
paymentId: paymentId,
|
||||
paymentIdValue: paymentIdValue,
|
||||
expirationTime: expirationTime,
|
||||
amount: amount,
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
@@ -95,13 +100,15 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
outputs: outputs,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
ConfirmSendingAlertContent(
|
||||
{this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
@@ -109,10 +116,12 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs});
|
||||
required this.outputs,
|
||||
required this.onDispose}) {}
|
||||
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
@@ -121,11 +130,13 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final Function? onDispose;
|
||||
|
||||
@override
|
||||
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
|
||||
paymentId: paymentId,
|
||||
paymentIdValue: paymentIdValue,
|
||||
expirationTime: expirationTime,
|
||||
amount: amount,
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
@@ -133,13 +144,15 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
outputs: outputs,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
|
||||
ConfirmSendingAlertContentState(
|
||||
{this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
@@ -147,7 +160,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs})
|
||||
required this.outputs,
|
||||
this.onDispose})
|
||||
: recipientTitle = '' {
|
||||
recipientTitle = outputs.length > 1
|
||||
? S.current.transaction_details_recipient_address
|
||||
@@ -156,6 +170,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
@@ -164,6 +179,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final Function? onDispose;
|
||||
|
||||
final double backgroundHeight = 160;
|
||||
final double thumbHeight = 72;
|
||||
@@ -172,6 +188,12 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
String recipientTitle;
|
||||
bool showScrollbar = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (onDispose != null) onDispose!();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
controller.addListener(() {
|
||||
@@ -217,14 +239,18 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
paymentIdValue!,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
Container(
|
||||
width: 160,
|
||||
child: Text(
|
||||
paymentIdValue!,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -232,6 +258,8 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.expirationTime != null)
|
||||
ExpirationTimeWidget(expirationTime: widget.expirationTime!),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -468,3 +496,46 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ExpirationTimeWidget extends StatelessWidget {
|
||||
const ExpirationTimeWidget({
|
||||
required this.expirationTime,
|
||||
});
|
||||
|
||||
final String expirationTime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 32),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.current.offer_expires_in,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
expirationTime,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/setting_action_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/setting_actions.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/router.dart' as Router;
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
@@ -10,7 +11,9 @@ import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
final _settingsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class DesktopSettingsPage extends StatefulWidget {
|
||||
const DesktopSettingsPage({super.key});
|
||||
const DesktopSettingsPage(this.dashboardViewModel, {super.key});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
|
||||
@@ -51,6 +54,12 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
|
||||
padding: EdgeInsets.only(top: 0),
|
||||
itemBuilder: (_, index) {
|
||||
final item = SettingActions.desktopSettings[index];
|
||||
|
||||
if (!widget.dashboardViewModel.hasSilentPayments &&
|
||||
item.name(context) == S.of(context).silent_payments_settings) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
return SettingActionButton(
|
||||
isLastTile: isLastTile,
|
||||
|
||||
@@ -106,6 +106,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
|
||||
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
|
||||
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
|
||||
final scrollController = ScrollController();
|
||||
final double tileHeight = 60;
|
||||
Flushbar<void>? _progressBar;
|
||||
@@ -319,6 +320,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
case WalletType.bitcoinCash:
|
||||
return bitcoinCashIcon;
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
return nanoIcon;
|
||||
case WalletType.polygon:
|
||||
return polygonIcon;
|
||||
@@ -326,7 +328,9 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
case WalletType.wownero:
|
||||
return wowneroIcon;
|
||||
case WalletType.none:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/date_picker.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -18,6 +20,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||
this.isSilentPaymentsScan = false,
|
||||
this.toggleSingleScan,
|
||||
this.doSingleScan = false,
|
||||
required this.walletType,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(int)? onHeightChange;
|
||||
@@ -27,6 +30,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||
final bool isSilentPaymentsScan;
|
||||
final bool doSingleScan;
|
||||
final Function()? toggleSingleScan;
|
||||
final WalletType walletType;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||
@@ -160,7 +164,13 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
if (widget.isSilentPaymentsScan) {
|
||||
height = bitcoin!.getHeightByDate(date: date);
|
||||
} else {
|
||||
height = monero!.getHeightByDate(date: date);
|
||||
if (widget.walletType == WalletType.monero) {
|
||||
height = monero!.getHeightByDate(date: date);
|
||||
} else {
|
||||
assert(widget.walletType == WalletType.wownero,
|
||||
"unknown currency in BlockchainHeightWidget");
|
||||
height = wownero!.getHeightByDate(date: date);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HavenWalletRemovalPopup extends StatelessWidget {
|
||||
final List<String> affectedWalletNames;
|
||||
|
||||
const HavenWalletRemovalPopup(this.affectedWalletNames, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
AlertBackground(
|
||||
child: AlertDialog(
|
||||
insetPadding: EdgeInsets.only(left: 16, right: 16, bottom: 48),
|
||||
elevation: 0.0,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30))),
|
||||
content: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.firstGradientBackgroundColor,
|
||||
Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.secondGradientBackgroundColor,
|
||||
], begin: Alignment.centerLeft, end: Alignment.centerRight)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
child: Text("Emergency Notice"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 48, bottom: 16),
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.7,
|
||||
),
|
||||
child: Text(
|
||||
"It looks like you have Haven wallets in your list. Haven is getting removed in next release of Cake Wallet, and you currently have Haven in the following wallets:\n\n[${affectedWalletNames.join(", ")}]\n\nPlease move your funds to other wallet, as you will lose access to your Haven funds in next update.\n\nFor assistance, please use the in-app support or email support@cakewallet.com",
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 16.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.textColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AlertCloseButton(bottom: 30)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class NumberTextField extends StatefulWidget {
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final int min;
|
||||
final int max;
|
||||
final int step;
|
||||
final double arrowsWidth;
|
||||
final double arrowsHeight;
|
||||
final EdgeInsets contentPadding;
|
||||
final double borderWidth;
|
||||
final ValueChanged<int?>? onChanged;
|
||||
|
||||
const NumberTextField({
|
||||
Key? key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.min = 0,
|
||||
this.max = 999,
|
||||
this.step = 1,
|
||||
this.arrowsWidth = 24,
|
||||
this.arrowsHeight = kMinInteractiveDimension,
|
||||
this.contentPadding = const EdgeInsets.symmetric(horizontal: 8),
|
||||
this.borderWidth = 2,
|
||||
this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NumberTextFieldState();
|
||||
}
|
||||
|
||||
class _NumberTextFieldState extends State<NumberTextField> {
|
||||
late TextEditingController _controller;
|
||||
late FocusNode _focusNode;
|
||||
bool _canGoUp = false;
|
||||
bool _canGoDown = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = widget.controller ?? TextEditingController();
|
||||
_focusNode = widget.focusNode ?? FocusNode();
|
||||
_updateArrows(int.tryParse(_controller.text));
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant NumberTextField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_controller = widget.controller ?? _controller;
|
||||
_focusNode = widget.focusNode ?? _focusNode;
|
||||
_updateArrows(int.tryParse(_controller.text));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextField(
|
||||
style: textMediumSemiBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
enableInteractiveSelection: false,
|
||||
textAlign: TextAlign.center,
|
||||
textAlignVertical: TextAlignVertical.bottom,
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
fillColor: Colors.transparent,
|
||||
counterText: '',
|
||||
isDense: true,
|
||||
filled: true,
|
||||
suffixIconConstraints: BoxConstraints(
|
||||
maxHeight: widget.arrowsHeight,
|
||||
maxWidth: widget.arrowsWidth + widget.contentPadding.right),
|
||||
prefixIconConstraints: BoxConstraints(
|
||||
maxHeight: widget.arrowsHeight,
|
||||
maxWidth: widget.arrowsWidth + widget.contentPadding.left),
|
||||
prefixIcon: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(Icons.arrow_left_outlined, size: widget.arrowsWidth)),
|
||||
onTap: _canGoDown ? () => _update(false) : null)),
|
||||
suffixIcon: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: widget.arrowsWidth,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Icon(Icons.arrow_right_outlined, size: widget.arrowsWidth)),
|
||||
onTap: _canGoUp ? () => _update(true) : null))),
|
||||
maxLines: 1,
|
||||
onChanged: (value) {
|
||||
final intValue = int.tryParse(value);
|
||||
widget.onChanged?.call(intValue);
|
||||
_updateArrows(intValue);
|
||||
},
|
||||
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
|
||||
|
||||
void _update(bool up) {
|
||||
var intValue = int.tryParse(_controller.text);
|
||||
intValue == null ? intValue = widget.min : intValue += up ? widget.step : -widget.step;
|
||||
intValue = intValue.clamp(widget.min, widget.max); // Ensure intValue is within range
|
||||
_controller.text = intValue.toString();
|
||||
|
||||
// Manually call the onChanged callback after updating the controller's text
|
||||
widget.onChanged?.call(intValue);
|
||||
|
||||
_updateArrows(intValue);
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
|
||||
void _updateArrows(int? value) {
|
||||
final canGoUp = value == null || value < widget.max;
|
||||
final canGoDown = value == null || value > widget.min;
|
||||
if (_canGoUp != canGoUp || _canGoDown != canGoDown)
|
||||
setState(() {
|
||||
_canGoUp = canGoUp;
|
||||
_canGoDown = canGoDown;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _NumberTextInputFormatter extends TextInputFormatter {
|
||||
final int min;
|
||||
final int max;
|
||||
|
||||
_NumberTextInputFormatter(this.min, this.max);
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
||||
if (const ['-', ''].contains(newValue.text)) return newValue;
|
||||
final intValue = int.tryParse(newValue.text);
|
||||
if (intValue == null) return oldValue;
|
||||
if (intValue < min) return newValue.copyWith(text: min.toString());
|
||||
if (intValue > max) return newValue.copyWith(text: max.toString());
|
||||
return newValue.copyWith(text: intValue.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:cake_wallet/core/seed_validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/core/seed_validator.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
|
||||
class SeedWidget extends StatefulWidget {
|
||||
SeedWidget({
|
||||
@@ -23,7 +23,6 @@ class SeedWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class SeedWidgetState extends State<SeedWidget> {
|
||||
|
||||
SeedWidgetState(String language, this.type)
|
||||
: controller = TextEditingController(),
|
||||
focusNode = FocusNode(),
|
||||
@@ -46,6 +45,7 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||
final FocusNode focusNode;
|
||||
final WalletType type;
|
||||
List<String> words;
|
||||
bool normalizeSeed = false;
|
||||
bool _showPlaceholder;
|
||||
|
||||
String get text => controller.text;
|
||||
@@ -60,6 +60,7 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||
void changeSeedLanguage(String language) {
|
||||
setState(() {
|
||||
words = SeedValidator.getWordList(type: type, language: language);
|
||||
normalizeSeed = SeedValidator.needsNormalization(language);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,6 +98,7 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
words: words,
|
||||
normalizeSeed: normalizeSeed,
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
backgroundColor: Colors.transparent,
|
||||
|
||||
@@ -29,6 +29,7 @@ class SettingActions {
|
||||
connectionSettingAction,
|
||||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:cw_core/utils/text_normalizer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
extension Compare<T> on Comparable<T> {
|
||||
bool operator <=(T other) => compareTo(other) <= 0;
|
||||
bool operator >=(T other) => compareTo(other) >= 0;
|
||||
@@ -39,6 +39,7 @@ class ValidatableAnnotatedEditableText extends EditableText {
|
||||
required this.validStyle,
|
||||
required this.invalidStyle,
|
||||
required this.words,
|
||||
this.normalizeSeed = false,
|
||||
TextStyle textStyle = const TextStyle(
|
||||
color: Colors.black,
|
||||
backgroundColor: Colors.transparent,
|
||||
@@ -74,6 +75,7 @@ class ValidatableAnnotatedEditableText extends EditableText {
|
||||
showSelectionHandles: true,
|
||||
showCursor: true);
|
||||
|
||||
final bool normalizeSeed;
|
||||
final List<String> words;
|
||||
final TextStyle validStyle;
|
||||
final TextStyle invalidStyle;
|
||||
@@ -137,7 +139,8 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validate(String source) => widget.words.indexOf(source) >= 0;
|
||||
bool validate(String source) =>
|
||||
widget.words.indexOf(widget.normalizeSeed ? normalizeText(source) : source) >= 0;
|
||||
|
||||
List<TextRange> range(String pattern, String source) {
|
||||
final result = <TextRange>[];
|
||||
@@ -173,4 +176,4 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
|
||||
|
||||
return TextSpan(style: widget.style, text: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user