Add Active Services page UI

Add Phone service entity
This commit is contained in:
OmarHatem28 2022-07-05 17:34:16 +02:00
parent 6fdcc0dd9e
commit 81e799c514
16 changed files with 422 additions and 36 deletions

View file

@ -0,0 +1,23 @@
class PhoneNumberService {
const PhoneNumberService({
this.id,
this.phoneNumber,
this.planId,
this.usedUntil,
this.messageReceiveEnabled,
this.autoRenew,
});
final String id;
final String phoneNumber;
final String planId;
final DateTime usedUntil;
final bool messageReceiveEnabled;
final bool autoRenew;
@override
bool operator ==(Object other) => other is PhoneNumberService && other.id == id;
@override
int get hashCode => id.hashCode;
}

View file

@ -69,6 +69,7 @@ import 'package:cake_wallet/src/screens/cake_phone/cake_phone_welcome_page.dart'
import 'package:cake_wallet/src/screens/cake_phone/cake_phone_verification_page.dart';
import 'package:cake_wallet/src/screens/cake_phone/cake_phone_products_page.dart';
import 'package:cake_wallet/src/screens/cake_phone/cake_phone_products/phone_number_product_page.dart';
import 'package:cake_wallet/src/screens/cake_phone/active_services_page.dart';
RouteSettings currentRouteSettings;
@ -409,6 +410,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.cakePhoneWelcome:
return MaterialPageRoute<CakePhoneWelcomePage>(
settings: RouteSettings(name: Routes.cakePhoneWelcome),
builder: (_) => CakePhoneWelcomePage(),
);
@ -416,24 +418,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
final isLogin = settings.arguments as bool ?? false;
return MaterialPageRoute<CakePhoneWelcomePage>(
settings: RouteSettings(name: Routes.cakePhoneAuth),
builder: (_) => CakePhoneAuthPage(isLogin: isLogin),
);
case Routes.cakePhoneVerification:
return MaterialPageRoute<CakePhoneVerificationPage>(
settings: RouteSettings(name: Routes.cakePhoneVerification),
builder: (_) => CakePhoneVerificationPage(),
);
case Routes.cakePhoneProducts:
return MaterialPageRoute<CakePhoneProductsPage>(
settings: RouteSettings(name: Routes.cakePhoneProducts),
builder: (_) => CakePhoneProductsPage(),
);
case Routes.phoneNumberProduct:
return MaterialPageRoute<PhoneNumberProductPage>(
settings: RouteSettings(name: Routes.phoneNumberProduct),
builder: (_) => PhoneNumberProductPage(getIt.get<PhonePlanViewModel>()),
);
case Routes.cakePhoneActiveServices:
return MaterialPageRoute<ActiveServicesPage>(
settings: RouteSettings(name: Routes.cakePhoneActiveServices),
builder: (_) => ActiveServicesPage(),
);
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -65,4 +65,5 @@ class Routes {
static const cakePhoneVerification = '/cake_phone_verification';
static const cakePhoneProducts = '/cake_phone_products';
static const phoneNumberProduct = '/phone_number_product';
static const cakePhoneActiveServices = '/cake_phone_active_services';
}

View file

@ -0,0 +1,127 @@
import 'package:cake_wallet/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/cake_phone/widgets/add_options_tile.dart';
import 'package:cake_wallet/src/screens/cake_phone/widgets/info_text_column.dart';
import 'package:cake_wallet/src/screens/cake_phone/widgets/subscribed_phone_numbers.dart';
class ActiveServicesPage extends BasePage {
ActiveServicesPage();
@override
Widget body(BuildContext context) => ActiveServicesBody();
@override
Widget middle(BuildContext context) {
return Text(
S.of(context).active_services,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: titleColor ?? Theme.of(context).primaryTextTheme.title.color),
);
}
}
class ActiveServicesBody extends StatefulWidget {
ActiveServicesBody();
@override
ActiveServicesBodyState createState() => ActiveServicesBodyState();
}
class ActiveServicesBodyState extends State<ActiveServicesBody> {
// TODO: remove const dummy variables
final int freeSMSCount = 50;
final int freeMBCount = 0;
final int serviceDaysLeft = 23;
final double accountBalance = 20.34;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Text(S.of(context).new_phone_number,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w700,
)),
),
const SizedBox(height: 8),
freeBalanceInfoRow(),
const SizedBox(height: 24),
AddOptionsTile(
leading: InfoTextColumn(
title: S.of(context).account_balance,
subtitle: "\$${accountBalance.toStringAsFixed(2)}",
isReversed: true,
),
),
const SizedBox(height: 64),
SubscribedPhoneNumbers(),
],
),
),
);
}
Widget freeBalanceInfoRow() {
return Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
color: Theme.of(context).primaryTextTheme.display3.decorationColor,
),
child: InfoTextColumn(
title: S.of(context).free_sms_balance,
subtitle: "${freeSMSCount} SMS",
padding: const EdgeInsets.only(top: 12, bottom: 4),
isReversed: true,
),
),
),
const SizedBox(width: 9),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
color: Theme.of(context).primaryTextTheme.display3.decorationColor,
),
child: InfoTextColumn(
title: S.of(context).free_data_balance,
subtitle: "${freeMBCount} MB",
padding: const EdgeInsets.only(top: 12, bottom: 4),
isReversed: true,
),
),
),
],
);
}
}

View file

@ -21,7 +21,7 @@ class CakePhoneAuthPage extends BasePage {
isLogin ? S.of(context).login : S.of(context).signup,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color),
);

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -14,7 +15,7 @@ import 'package:flutter/cupertino.dart';
import 'package:country_pickers/country_pickers.dart';
import 'package:country_pickers/countries.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/entities/service_plan.dart';
import 'package:cake_wallet/entities/cake_phone_entities/service_plan.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
@ -34,7 +35,7 @@ class PhoneNumberProductPage extends BasePage {
S.of(context).phone_number,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color),
);
@ -336,7 +337,7 @@ class PhoneNumberProductBodyState extends State<PhoneNumberProductBody> {
mainAxisSize: MainAxisSize.min,
children: [
receiptRow(S.of(context).amount, amountText(phonePlanViewModel.totalPrice)),
receiptRow(S.of(context).cake_pay_balance, amountText(100)),
receiptRow("${S.of(context).cake_pay_balance}: ", amountText(100)),
],
),
isDividerExists: true,
@ -344,6 +345,11 @@ class PhoneNumberProductBodyState extends State<PhoneNumberProductBody> {
leftButtonText: S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext).pop();
Navigator.pushNamedAndRemoveUntil(
context,
Routes.cakePhoneActiveServices,
ModalRoute.withName(Routes.cakePhoneWelcome),
);
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
});
@ -452,7 +458,7 @@ class PhoneNumberProductBodyState extends State<PhoneNumberProductBody> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"$title:",
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@ -478,31 +484,42 @@ class PhoneNumberProductBodyState extends State<PhoneNumberProductBody> {
Widget cryptoAmount(double totalPrice) {
return FutureBuilder<BuyAmount>(
future: MoonPayBuyProvider(wallet: getIt.get<AppStore>().wallet)
.calculateAmount(totalPrice.toString(), FiatCurrency.usd.title),
builder: (context, AsyncSnapshot<BuyAmount> snapshot) {
double sourceAmount;
double destAmount;
double achAmount;
int minAmount;
future: MoonPayBuyProvider(wallet: getIt.get<AppStore>().wallet)
.calculateAmount(totalPrice.toString(), FiatCurrency.usd.title),
builder: (context, AsyncSnapshot<BuyAmount> snapshot) {
double sourceAmount;
double destAmount;
if (snapshot.hasData) {
sourceAmount = snapshot.data.sourceAmount;
destAmount = snapshot.data.destAmount;
minAmount = snapshot.data.minAmount;
achAmount = snapshot.data.achSourceAmount;
} else {
sourceAmount = 0.0;
destAmount = 0.0;
minAmount = 0;
}
if (snapshot.hasData) {
sourceAmount = snapshot.data.sourceAmount;
destAmount = snapshot.data.destAmount;
} else {
sourceAmount = 0.0;
destAmount = 0.0;
}
return Column(
children: [
Text(sourceAmount.toString()),
Text(destAmount.toString()),
],
);
});
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${sourceAmount} ${getIt.get<AppStore>().wallet.currency.toString()}",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Theme.of(context).primaryTextTheme.title.color,
),
),
Text(
"${destAmount} ${FiatCurrency.usd.title}",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.subhead.color,
),
),
],
);
},
);
}
}

View file

@ -16,7 +16,7 @@ class CakePhoneProductsPage extends BasePage {
S.of(context).get_phone_number,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color),
);

View file

@ -21,7 +21,7 @@ class CakePhoneVerificationPage extends BasePage {
S.of(context).email_verification,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color),
);
@ -123,7 +123,11 @@ class CakePhoneVerificationBodyState extends State<CakePhoneVerificationBody> {
children: <Widget>[
PrimaryButton(
onPressed: () {
Navigator.pushNamed(context, Routes.cakePhoneProducts);
Navigator.pushNamedAndRemoveUntil(
context,
Routes.cakePhoneProducts,
ModalRoute.withName(Routes.cakePhoneWelcome),
);
},
text: S.of(context).continue_text,
color: Theme.of(context).accentTextTheme.body2.color,

View file

@ -18,7 +18,7 @@ class CakePhoneWelcomePage extends BasePage {
S.of(context).welcome_to_cake_phone,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
fontFamily: 'Lato',
color: titleColor ??
Theme.of(context).primaryTextTheme.title.color),

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class AddOptionsTile extends StatelessWidget {
const AddOptionsTile({Key key, @required this.leading, this.onTap}) : super(key: key);
final Widget leading;
final Function() onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
color: Theme.of(context).primaryTextTheme.display3.decorationColor,
),
child: Row(
children: [
Expanded(
child: leading,
),
Container(
width: 2,
height: 48,
color: Theme.of(context).primaryTextTheme.title.color,
),
const SizedBox(width: 16),
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).primaryTextTheme.title.color,
),
child: Icon(Icons.add, color: Colors.white, size: 15),
),
],
),
),
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
class InfoTextColumn extends StatelessWidget {
const InfoTextColumn({
Key key,
@required this.title,
@required this.subtitle,
this.isReversed = false,
this.padding,
}) : super(key: key);
final String title;
final String subtitle;
final bool isReversed;
final EdgeInsets padding;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: isReversed ? subtitleTextStyle(context) : titleTextStyle(context),
),
Padding(
padding: padding ?? const EdgeInsets.only(top: 4),
child: Text(
subtitle,
style: isReversed ? titleTextStyle(context) : subtitleTextStyle(context),
),
),
],
);
}
TextStyle subtitleTextStyle(BuildContext context) => TextStyle(
fontSize: 12,
color: Theme.of(context).accentTextTheme.subhead.color,
);
TextStyle titleTextStyle(BuildContext context) => TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).primaryTextTheme.title.color,
);
}

View file

@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/entities/cake_phone_entities/phone_number_service.dart';
import 'package:cake_wallet/src/screens/cake_phone/widgets/add_options_tile.dart';
import 'package:cake_wallet/src/screens/cake_phone/widgets/info_text_column.dart';
class SubscribedPhoneNumbers extends StatefulWidget {
const SubscribedPhoneNumbers({Key key}) : super(key: key);
@override
_SubscribedPhoneNumbersState createState() => _SubscribedPhoneNumbersState();
}
class _SubscribedPhoneNumbersState extends State<SubscribedPhoneNumbers> {
int selectedTab = 0;
final List<PhoneNumberService> subscribedPhoneNumbers = [
PhoneNumberService(id: "1", phoneNumber: "+1 888-888-8888", usedUntil: DateTime.now().add(Duration(days: 24))),
PhoneNumberService(id: "2", phoneNumber: "+1 888-888-8888", usedUntil: DateTime.now().add(Duration(days: 26))),
PhoneNumberService(id: "3", phoneNumber: "+1 999-999-9999", usedUntil: DateTime.now().subtract(Duration(days: 24))),
PhoneNumberService(id: "4", phoneNumber: "+1 999-999-9999", usedUntil: DateTime.now().subtract(Duration(days: 26))),
];
final List<PhoneNumberService> activePhoneNumbers = [];
final List<PhoneNumberService> expiredPhoneNumbers = [];
@override
void initState() {
super.initState();
for (PhoneNumberService element in subscribedPhoneNumbers) {
if (element.usedUntil.isAfter(DateTime.now())) {
activePhoneNumbers.add(element);
} else {
expiredPhoneNumbers.add(element);
}
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: Theme.of(context).primaryTextTheme.display3.decorationColor,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
tab(S.of(context).active, 0),
tab(S.of(context).expired, 1),
],
),
),
const SizedBox(height: 16),
...(selectedTab == 0 ? activePhoneNumbers : expiredPhoneNumbers).map(
(e) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: AddOptionsTile(
leading: InfoTextColumn(
title: e.phoneNumber,
subtitle: selectedTab == 0
? "${e.usedUntil.difference(DateTime.now()).inDays} ${S.of(context).days_of_service_remaining}"
: S.of(context).expired,
),
),
),
),
],
);
}
Widget tab(String title, int tabIndex) {
final selected = selectedTab == tabIndex;
return GestureDetector(
onTap: () {
if (!selected) {
setState(() {
selectedTab = tabIndex;
});
}
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: selected
? Theme.of(context).accentTextTheme.body2.color
: Theme.of(context).primaryTextTheme.display3.decorationColor,
),
child: Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? Colors.white : Theme.of(context).primaryTextTheme.title.color,
),
),
),
);
}
}

View file

@ -82,7 +82,7 @@ class WalletMenu {
handler: () => Navigator.of(context).pushNamed(Routes.support)),
// TODO: Move this to marketplace screen when ready
WalletMenuItem(
title: S.current.settings_support,
title: "Temp Cake Phone",
image: Image.asset('assets/images/question_mark.png',
height: 16, width: 16, color: Palette.darkBlue),
handler: () => Navigator.of(context).pushNamed(Routes.cakePhoneWelcome)),

View file

@ -1,4 +1,4 @@
import 'package:cake_wallet/entities/service_plan.dart';
import 'package:cake_wallet/entities/cake_phone_entities/service_plan.dart';
import 'package:country_pickers/countries.dart';
import 'package:country_pickers/country.dart';
import 'package:mobx/mobx.dart';

View file

@ -568,5 +568,12 @@
"phone_number_country": "Phone Number Country",
"additional_sms_messages": "Additional SMS Messages",
"confirm_payment": "Confirm payment",
"cake_pay_balance": "CakePay Balance"
"cake_pay_balance": "CakePay Balance",
"active_services": "Active Services",
"new_phone_number": "New Phone Number",
"free_sms_balance": "Free SMS Balance",
"free_data_balance": "Free Data Balance",
"account_balance": "Account Balance",
"days_of_service_remaining": "days of service remaining",
"active": "Active"
}