plan upgrade does work

This commit is contained in:
otsmr 2025-05-09 23:11:03 +02:00
parent a795aea308
commit e7129614fa
15 changed files with 783 additions and 178 deletions

View file

@ -169,13 +169,12 @@
"errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.", "errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.",
"errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.", "errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.",
"errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.", "errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.",
"proYearlyPrice": "10€/Jahr", "errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.",
"proMonthlyPrice": "1€/Monat",
"proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
"proFeature2": "1 zusätzlicher Plus Benutzer", "proFeature2": "1 zusätzlicher Plus Benutzer",
"proFeature3": "3 zusätzliche kostenlose Benutzer", "proFeature3": "3 zusätzliche kostenlose Benutzer",
"familyYearlyPrice": "20€/Jahr", "year": "year",
"familyMonthlyPrice": "2€/Monat", "month": "month",
"familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
"familyFeature2": "4 zusätzliche Plus Benutzer", "familyFeature2": "4 zusätzliche Plus Benutzer",
"familyFeature3": "5 zusätzliche kostenlose Benutzer", "familyFeature3": "5 zusätzliche kostenlose Benutzer",
@ -183,7 +182,7 @@
"freeFeature1": "3 Medien-Datei-Uploads pro Tag", "freeFeature1": "3 Medien-Datei-Uploads pro Tag",
"plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
"transactionHistory": "Transaktionshistorie", "transactionHistory": "Transaktionshistorie",
"currentBalance": "Aktueller Kontostand", "currentBalance": "Dein Guthaben",
"manageAdditionalUsers": "Zusätzlichen Benutzer verwalten", "manageAdditionalUsers": "Zusätzlichen Benutzer verwalten",
"open": "Offene", "open": "Offene",
"buy": "Kaufen", "buy": "Kaufen",
@ -203,5 +202,16 @@
"transactionThanksForTesting": "Danke fürs Testen", "transactionThanksForTesting": "Danke fürs Testen",
"transactionUnknown": "Unbekannte Transaktion", "transactionUnknown": "Unbekannte Transaktion",
"transactionVoucherCreated": "Gutschein erstellt", "transactionVoucherCreated": "Gutschein erstellt",
"transactionVoucherRedeemed": "Gutschein eingelöst" "transactionVoucherRedeemed": "Gutschein eingelöst",
"checkoutOptions": "Optionen",
"checkoutPayYearly": "Jährlich bezahlen",
"checkoutTotal": "Gesamt",
"selectPaymentMethode": "Zahlungsmethode auswählen",
"twonlyCredit": "twonly-Guthaben",
"notEnoughCredit": "Du hast nicht genügend Guthaben!",
"chargeCredit": "Guthaben aufladen",
"autoRenewal": "Automatische Verlängerung",
"autoRenewalDesc": "Du kannst dies jederzeit ändern.",
"planSuccessUpgraded": "Dein Plan wurde erfolgreich aktualisiert.",
"checkoutSubmit": "Kostenpflichtig bestellen"
} }

View file

@ -326,14 +326,13 @@
"errorVoucherInvalid": "The voucher code you entered is not valid.", "errorVoucherInvalid": "The voucher code you entered is not valid.",
"errorPlanLimitReached": "You have reached your plans limit. Please upgrade your plan.", "errorPlanLimitReached": "You have reached your plans limit. Please upgrade your plan.",
"errorPlanNotAllowed": "This feature is not available in your current plan.", "errorPlanNotAllowed": "This feature is not available in your current plan.",
"errorPlanUpgradeNotYearly": "The plan upgrade must be paid for annually, as the current plan is also billed annually.",
"upgradeToPaidPlan": "Upgrade to a paid plan.", "upgradeToPaidPlan": "Upgrade to a paid plan.",
"proYearlyPrice": "10€/year", "year": "year",
"proMonthlyPrice": "1€/month", "month": "month",
"proFeature1": "✓ Unlimited media file uploads", "proFeature1": "✓ Unlimited media file uploads",
"proFeature2": "1 additional Plus user", "proFeature2": "1 additional Plus user",
"proFeature3": "3 additional Free users", "proFeature3": "3 additional Free users",
"familyYearlyPrice": "20€/year",
"familyMonthlyPrice": "2€/month",
"familyFeature1": "✓ Unlimited media file uploads", "familyFeature1": "✓ Unlimited media file uploads",
"familyFeature2": "4 additional Plus users", "familyFeature2": "4 additional Plus users",
"familyFeature3": "5 additional Free users", "familyFeature3": "5 additional Free users",
@ -341,6 +340,8 @@
"freeFeature1": "3 Media file uploads per day", "freeFeature1": "3 Media file uploads per day",
"plusFeature1": "✓ Unlimited media file uploads", "plusFeature1": "✓ Unlimited media file uploads",
"transactionHistory": "Your transaction history", "transactionHistory": "Your transaction history",
"manageSubscription": "Manage your subscription",
"nextPayment": "Next payment",
"currentBalance": "Current balance", "currentBalance": "Current balance",
"manageAdditionalUsers": "Manage your additional users", "manageAdditionalUsers": "Manage your additional users",
"open": "Open", "open": "Open",
@ -361,5 +362,17 @@
"transactionThanksForTesting": "Thank you for testing", "transactionThanksForTesting": "Thank you for testing",
"transactionUnknown": "Unknown transaction", "transactionUnknown": "Unknown transaction",
"transactionVoucherCreated": "Voucher created", "transactionVoucherCreated": "Voucher created",
"transactionVoucherRedeemed": "Voucher redeemed" "transactionVoucherRedeemed": "Voucher redeemed",
"checkoutOptions": "Options",
"checkoutPayYearly": "Pay yearly",
"checkoutTotal": "Total",
"selectPaymentMethode": "Select Payment Method",
"twonlyCredit": "twonly-Credit",
"notEnoughCredit": "You do not have enough credit!",
"chargeCredit": "Charge credit",
"chargeCredit": "Charge credit",
"autoRenewal": "Auto renewal",
"autoRenewalDesc": "You can change this at any time.",
"planSuccessUpgraded": "Successfully upgraded your plan.",
"checkoutSubmit": "Order with a fee."
} }

View file

@ -1019,23 +1019,29 @@ abstract class AppLocalizations {
/// **'This feature is not available in your current plan.'** /// **'This feature is not available in your current plan.'**
String get errorPlanNotAllowed; String get errorPlanNotAllowed;
/// No description provided for @errorPlanUpgradeNotYearly.
///
/// In en, this message translates to:
/// **'The plan upgrade must be paid for annually, as the current plan is also billed annually.'**
String get errorPlanUpgradeNotYearly;
/// No description provided for @upgradeToPaidPlan. /// No description provided for @upgradeToPaidPlan.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Upgrade to a paid plan.'** /// **'Upgrade to a paid plan.'**
String get upgradeToPaidPlan; String get upgradeToPaidPlan;
/// No description provided for @proYearlyPrice. /// No description provided for @year.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'10€/year'** /// **'year'**
String get proYearlyPrice; String get year;
/// No description provided for @proMonthlyPrice. /// No description provided for @month.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'1€/month'** /// **'month'**
String get proMonthlyPrice; String get month;
/// No description provided for @proFeature1. /// No description provided for @proFeature1.
/// ///
@ -1055,18 +1061,6 @@ abstract class AppLocalizations {
/// **'3 additional Free users'** /// **'3 additional Free users'**
String get proFeature3; String get proFeature3;
/// No description provided for @familyYearlyPrice.
///
/// In en, this message translates to:
/// **'20€/year'**
String get familyYearlyPrice;
/// No description provided for @familyMonthlyPrice.
///
/// In en, this message translates to:
/// **'2€/month'**
String get familyMonthlyPrice;
/// No description provided for @familyFeature1. /// No description provided for @familyFeature1.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -1109,6 +1103,18 @@ abstract class AppLocalizations {
/// **'Your transaction history'** /// **'Your transaction history'**
String get transactionHistory; String get transactionHistory;
/// No description provided for @manageSubscription.
///
/// In en, this message translates to:
/// **'Manage your subscription'**
String get manageSubscription;
/// No description provided for @nextPayment.
///
/// In en, this message translates to:
/// **'Next payment'**
String get nextPayment;
/// No description provided for @currentBalance. /// No description provided for @currentBalance.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -1234,6 +1240,72 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Voucher redeemed'** /// **'Voucher redeemed'**
String get transactionVoucherRedeemed; String get transactionVoucherRedeemed;
/// No description provided for @checkoutOptions.
///
/// In en, this message translates to:
/// **'Options'**
String get checkoutOptions;
/// No description provided for @checkoutPayYearly.
///
/// In en, this message translates to:
/// **'Pay yearly'**
String get checkoutPayYearly;
/// No description provided for @checkoutTotal.
///
/// In en, this message translates to:
/// **'Total'**
String get checkoutTotal;
/// No description provided for @selectPaymentMethode.
///
/// In en, this message translates to:
/// **'Select Payment Method'**
String get selectPaymentMethode;
/// No description provided for @twonlyCredit.
///
/// In en, this message translates to:
/// **'twonly-Credit'**
String get twonlyCredit;
/// No description provided for @notEnoughCredit.
///
/// In en, this message translates to:
/// **'You do not have enough credit!'**
String get notEnoughCredit;
/// No description provided for @chargeCredit.
///
/// In en, this message translates to:
/// **'Charge credit'**
String get chargeCredit;
/// No description provided for @autoRenewal.
///
/// In en, this message translates to:
/// **'Auto renewal'**
String get autoRenewal;
/// No description provided for @autoRenewalDesc.
///
/// In en, this message translates to:
/// **'You can change this at any time.'**
String get autoRenewalDesc;
/// No description provided for @planSuccessUpgraded.
///
/// In en, this message translates to:
/// **'Successfully upgraded your plan.'**
String get planSuccessUpgraded;
/// No description provided for @checkoutSubmit.
///
/// In en, this message translates to:
/// **'Order with a fee.'**
String get checkoutSubmit;
} }
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {

View file

@ -482,14 +482,17 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get errorPlanNotAllowed => 'Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.'; String get errorPlanNotAllowed => 'Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.';
@override
String get errorPlanUpgradeNotYearly => 'Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.';
@override @override
String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.'; String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.';
@override @override
String get proYearlyPrice => '10€/Jahr'; String get year => 'year';
@override @override
String get proMonthlyPrice => '1€/Monat'; String get month => 'month';
@override @override
String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
@ -500,12 +503,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get proFeature3 => '3 zusätzliche kostenlose Benutzer'; String get proFeature3 => '3 zusätzliche kostenlose Benutzer';
@override
String get familyYearlyPrice => '20€/Jahr';
@override
String get familyMonthlyPrice => '2€/Monat';
@override @override
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
@ -528,7 +525,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get transactionHistory => 'Transaktionshistorie'; String get transactionHistory => 'Transaktionshistorie';
@override @override
String get currentBalance => 'Aktueller Kontostand'; String get manageSubscription => 'Manage your subscription';
@override
String get nextPayment => 'Next payment';
@override
String get currentBalance => 'Dein Guthaben';
@override @override
String get manageAdditionalUsers => 'Zusätzlichen Benutzer verwalten'; String get manageAdditionalUsers => 'Zusätzlichen Benutzer verwalten';
@ -589,4 +592,37 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get transactionVoucherRedeemed => 'Gutschein eingelöst'; String get transactionVoucherRedeemed => 'Gutschein eingelöst';
@override
String get checkoutOptions => 'Optionen';
@override
String get checkoutPayYearly => 'Jährlich bezahlen';
@override
String get checkoutTotal => 'Gesamt';
@override
String get selectPaymentMethode => 'Zahlungsmethode auswählen';
@override
String get twonlyCredit => 'twonly-Guthaben';
@override
String get notEnoughCredit => 'Du hast nicht genügend Guthaben!';
@override
String get chargeCredit => 'Guthaben aufladen';
@override
String get autoRenewal => 'Automatische Verlängerung';
@override
String get autoRenewalDesc => 'Du kannst dies jederzeit ändern.';
@override
String get planSuccessUpgraded => 'Dein Plan wurde erfolgreich aktualisiert.';
@override
String get checkoutSubmit => 'Kostenpflichtig bestellen';
} }

View file

@ -482,14 +482,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get errorPlanNotAllowed => 'This feature is not available in your current plan.'; String get errorPlanNotAllowed => 'This feature is not available in your current plan.';
@override
String get errorPlanUpgradeNotYearly => 'The plan upgrade must be paid for annually, as the current plan is also billed annually.';
@override @override
String get upgradeToPaidPlan => 'Upgrade to a paid plan.'; String get upgradeToPaidPlan => 'Upgrade to a paid plan.';
@override @override
String get proYearlyPrice => '10€/year'; String get year => 'year';
@override @override
String get proMonthlyPrice => '1€/month'; String get month => 'month';
@override @override
String get proFeature1 => '✓ Unlimited media file uploads'; String get proFeature1 => '✓ Unlimited media file uploads';
@ -500,12 +503,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get proFeature3 => '3 additional Free users'; String get proFeature3 => '3 additional Free users';
@override
String get familyYearlyPrice => '20€/year';
@override
String get familyMonthlyPrice => '2€/month';
@override @override
String get familyFeature1 => '✓ Unlimited media file uploads'; String get familyFeature1 => '✓ Unlimited media file uploads';
@ -527,6 +524,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get transactionHistory => 'Your transaction history'; String get transactionHistory => 'Your transaction history';
@override
String get manageSubscription => 'Manage your subscription';
@override
String get nextPayment => 'Next payment';
@override @override
String get currentBalance => 'Current balance'; String get currentBalance => 'Current balance';
@ -589,4 +592,37 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get transactionVoucherRedeemed => 'Voucher redeemed'; String get transactionVoucherRedeemed => 'Voucher redeemed';
@override
String get checkoutOptions => 'Options';
@override
String get checkoutPayYearly => 'Pay yearly';
@override
String get checkoutTotal => 'Total';
@override
String get selectPaymentMethode => 'Select Payment Method';
@override
String get twonlyCredit => 'twonly-Credit';
@override
String get notEnoughCredit => 'You do not have enough credit!';
@override
String get chargeCredit => 'Charge credit';
@override
String get autoRenewal => 'Auto renewal';
@override
String get autoRenewalDesc => 'You can change this at any time.';
@override
String get planSuccessUpgraded => 'Successfully upgraded your plan.';
@override
String get checkoutSubmit => 'Order with a fee.';
} }

View file

@ -919,6 +919,7 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage {
factory ApplicationData_SwitchToPayedPlan({ factory ApplicationData_SwitchToPayedPlan({
$core.String? planId, $core.String? planId,
$core.bool? payMonthly, $core.bool? payMonthly,
$core.bool? autoRenewal,
}) { }) {
final $result = create(); final $result = create();
if (planId != null) { if (planId != null) {
@ -927,6 +928,9 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage {
if (payMonthly != null) { if (payMonthly != null) {
$result.payMonthly = payMonthly; $result.payMonthly = payMonthly;
} }
if (autoRenewal != null) {
$result.autoRenewal = autoRenewal;
}
return $result; return $result;
} }
ApplicationData_SwitchToPayedPlan._() : super(); ApplicationData_SwitchToPayedPlan._() : super();
@ -936,6 +940,7 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.SwitchToPayedPlan', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.SwitchToPayedPlan', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'planId') ..aOS(1, _omitFieldNames ? '' : 'planId')
..aOB(2, _omitFieldNames ? '' : 'payMonthly') ..aOB(2, _omitFieldNames ? '' : 'payMonthly')
..aOB(3, _omitFieldNames ? '' : 'autoRenewal')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -977,6 +982,15 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage {
$core.bool hasPayMonthly() => $_has(1); $core.bool hasPayMonthly() => $_has(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
void clearPayMonthly() => clearField(2); void clearPayMonthly() => clearField(2);
@$pb.TagNumber(3)
$core.bool get autoRenewal => $_getBF(2);
@$pb.TagNumber(3)
set autoRenewal($core.bool v) { $_setBool(2, v); }
@$pb.TagNumber(3)
$core.bool hasAutoRenewal() => $_has(2);
@$pb.TagNumber(3)
void clearAutoRenewal() => clearField(3);
} }
class ApplicationData_CreateVoucher extends $pb.GeneratedMessage { class ApplicationData_CreateVoucher extends $pb.GeneratedMessage {

View file

@ -211,6 +211,7 @@ const ApplicationData_SwitchToPayedPlan$json = {
'2': [ '2': [
{'1': 'plan_id', '3': 1, '4': 1, '5': 9, '10': 'planId'}, {'1': 'plan_id', '3': 1, '4': 1, '5': 9, '10': 'planId'},
{'1': 'pay_monthly', '3': 2, '4': 1, '5': 8, '10': 'payMonthly'}, {'1': 'pay_monthly', '3': 2, '4': 1, '5': 8, '10': 'payMonthly'},
{'1': 'auto_renewal', '3': 3, '4': 1, '5': 8, '10': 'autoRenewal'},
], ],
}; };
@ -330,19 +331,19 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'F0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEg' 'F0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEg'
'ASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgAS' 'ASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgAS'
'gJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGikK' 'gJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGikK'
'DVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpNChFTd2l0Y2hUb1BheW' 'DVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpwChFTd2l0Y2hUb1BheW'
'VkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5X21vbnRobHkYAiABKAhSCnBh' 'VkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5X21vbnRobHkYAiABKAhSCnBh'
'eU1vbnRobHkaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW' 'eU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUmVuZXdhbBowCg1DcmVhdGVWb3'
'50cxoNCgtHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFucxoX' 'VjaGVyEh8KC3ZhbHVlX2NlbnRzGAEgASgNUgp2YWx1ZUNlbnRzGg0KC0dldExvY2F0aW9uGg0K'
'ChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZWRlZW' 'C0dldFZvdWNoZXJzGhMKEUdldEF2YWlsYWJsZVBsYW5zGhcKFUdldEFkZEFjY291bnRzSW52aX'
'1BZGRpdGlvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRotChJHZXRQ' 'RlcxoVChNHZXRDdXJyZW50UGxhbkluZm9zGjcKFFJlZGVlbUFkZGl0aW9uYWxDb2RlEh8KC2lu'
'cmVrZXlzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGjsKDkdldFVwbG9hZFRva2' 'dml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2'
'VuEikKEHJlY2lwaWVudHNfY291bnQYASABKA1SD3JlY2lwaWVudHNDb3VudBqJAQoKVXBsb2Fk' 'VyX2lkGAEgASgDUgZ1c2VySWQaOwoOR2V0VXBsb2FkVG9rZW4SKQoQcmVjaXBpZW50c19jb3Vu'
'RGF0YRIhCgx1cGxvYWRfdG9rZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDV' 'dBgBIAEoDVIPcmVjaXBpZW50c0NvdW50GokBCgpVcGxvYWREYXRhEiEKDHVwbG9hZF90b2tlbh'
'IGb2Zmc2V0EhIKBGRhdGEYAyABKAxSBGRhdGESHwoIY2hlY2tzdW0YBCABKAxIAFIIY2hlY2tz' 'gBIAEoDFILdXBsb2FkVG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXQSEgoEZGF0YRgDIAEo'
'dW2IAQFCCwoJX2NoZWNrc3VtGk0KDERvd25sb2FkRGF0YRIlCg5kb3dubG9hZF90b2tlbhgBIA' 'DFIEZGF0YRIfCghjaGVja3N1bRgEIAEoDEgAUghjaGVja3N1bYgBAUILCglfY2hlY2tzdW0aTQ'
'EoDFINZG93bmxvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldEIRCg9BcHBsaWNhdGlv' 'oMRG93bmxvYWREYXRhEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuEhYK'
'bkRhdGE='); 'Bm9mZnNldBgCIAEoDVIGb2Zmc2V0QhEKD0FwcGxpY2F0aW9uRGF0YQ==');
@$core.Deprecated('Use responseDescriptor instead') @$core.Deprecated('Use responseDescriptor instead')
const Response$json = { const Response$json = {

View file

@ -42,6 +42,7 @@ class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode PlanLimitReached = ErrorCode._(1023, _omitEnumNames ? '' : 'PlanLimitReached'); static const ErrorCode PlanLimitReached = ErrorCode._(1023, _omitEnumNames ? '' : 'PlanLimitReached');
static const ErrorCode NotEnoughCredit = ErrorCode._(1024, _omitEnumNames ? '' : 'NotEnoughCredit'); static const ErrorCode NotEnoughCredit = ErrorCode._(1024, _omitEnumNames ? '' : 'NotEnoughCredit');
static const ErrorCode PlanDowngrade = ErrorCode._(1025, _omitEnumNames ? '' : 'PlanDowngrade'); static const ErrorCode PlanDowngrade = ErrorCode._(1025, _omitEnumNames ? '' : 'PlanDowngrade');
static const ErrorCode PlanUpgradeNotYearly = ErrorCode._(1026, _omitEnumNames ? '' : 'PlanUpgradeNotYearly');
static const $core.List<ErrorCode> values = <ErrorCode> [ static const $core.List<ErrorCode> values = <ErrorCode> [
Unknown, Unknown,
@ -72,6 +73,7 @@ class ErrorCode extends $pb.ProtobufEnum {
PlanLimitReached, PlanLimitReached,
NotEnoughCredit, NotEnoughCredit,
PlanDowngrade, PlanDowngrade,
PlanUpgradeNotYearly,
]; ];
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values); static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

View file

@ -45,6 +45,7 @@ const ErrorCode$json = {
{'1': 'PlanLimitReached', '2': 1023}, {'1': 'PlanLimitReached', '2': 1023},
{'1': 'NotEnoughCredit', '2': 1024}, {'1': 'NotEnoughCredit', '2': 1024},
{'1': 'PlanDowngrade', '2': 1025}, {'1': 'PlanDowngrade', '2': 1025},
{'1': 'PlanUpgradeNotYearly', '2': 1026},
], ],
}; };
@ -62,5 +63,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
'4Q+QcSGAoTQXBpRW5kcG9pbnROb3RGb3VuZBD6BxIWChFBdXRoVG9rZW5Ob3RWYWxpZBD7BxIT' '4Q+QcSGAoTQXBpRW5kcG9pbnROb3RGb3VuZBD6BxIWChFBdXRoVG9rZW5Ob3RWYWxpZBD7BxIT'
'Cg5JbnZhbGlkUHJlS2V5cxD8BxITCg5Wb3VjaGVySW5WYWxpZBD9BxITCg5QbGFuTm90QWxsb3' 'Cg5JbnZhbGlkUHJlS2V5cxD8BxITCg5Wb3VjaGVySW5WYWxpZBD9BxITCg5QbGFuTm90QWxsb3'
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q' 'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
'bGFuRG93bmdyYWRlEIEI'); 'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIII');

View file

@ -440,6 +440,17 @@ class ApiProvider {
return await sendRequestSync(req); return await sendRequestSync(req);
} }
Future<Result> switchToPayedPlan(
String planId, bool payMonthly, bool autoRenewal) async {
var get = ApplicationData_SwitchToPayedPlan()
..planId = planId
..payMonthly = payMonthly
..autoRenewal = autoRenewal;
var appData = ApplicationData()..switchtopayedplan = get;
var req = createClientToServerFromApplicationData(appData);
return await sendRequestSync(req);
}
Future<Result> redeemVoucher(String voucher) async { Future<Result> redeemVoucher(String voucher) async {
var get = ApplicationData_RedeemVoucher()..voucher = voucher; var get = ApplicationData_RedeemVoucher()..voucher = voucher;
var appData = ApplicationData()..redeemvoucher = get; var appData = ApplicationData()..redeemvoucher = get;

View file

@ -85,6 +85,8 @@ String errorCodeToText(BuildContext context, ErrorCode code) {
return context.lang.errorPlanNotAllowed; return context.lang.errorPlanNotAllowed;
case ErrorCode.VoucherInValid: case ErrorCode.VoucherInValid:
return context.lang.errorVoucherInvalid; return context.lang.errorVoucherInvalid;
case ErrorCode.PlanUpgradeNotYearly:
return context.lang.errorPlanUpgradeNotYearly;
default: default:
return code.toString(); // Fallback for unrecognized keys return code.toString(); // Fallback for unrecognized keys
} }

View file

@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/settings/subscription/select_payment.dart';
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
class CheckoutView extends StatefulWidget {
const CheckoutView({
super.key,
required this.planId,
});
final String planId;
@override
State<CheckoutView> createState() => _CheckoutViewState();
}
class _CheckoutViewState extends State<CheckoutView> {
int checkoutInCents = 0;
bool paidMonthly = false;
bool tryAutoRenewal = true;
@override
void initState() {
super.initState();
setCheckout(true);
}
void setCheckout(bool init) {
checkoutInCents = getPlanPrice(widget.planId, paidMonthly);
if (!init) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
String totalPrice =
"${localePrizing(context, checkoutInCents)}/${(paidMonthly) ? context.lang.month : context.lang.year}";
return Scaffold(
appBar: AppBar(
title: Text(context.lang.checkoutOptions),
),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ListView(
children: [
PlanCard(planId: widget.planId),
Padding(
padding: const EdgeInsets.all(16.0),
child: ListTile(
title: Text(context.lang.checkoutPayYearly),
onTap: () {
paidMonthly = !paidMonthly;
setCheckout(false);
},
trailing: Checkbox(
value: !paidMonthly,
onChanged: (a) {
paidMonthly = !paidMonthly;
setCheckout(false);
},
),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(16),
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
context.lang.checkoutTotal,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
totalPrice,
textAlign: TextAlign.end,
),
],
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: FilledButton(
onPressed: () async {
bool? success = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return SelectPaymentView(
planId: widget.planId, payMonthly: paidMonthly);
}));
if (success != null && success && context.mounted) {
Navigator.pop(context);
}
},
child: Text(context.lang.selectPaymentMethode)),
),
SizedBox(height: 20)
],
),
),
);
}
}

View file

@ -0,0 +1,251 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
import 'package:twonly/src/views/settings/subscription/voucher_view.dart';
import 'package:url_launcher/url_launcher.dart';
class SelectPaymentView extends StatefulWidget {
const SelectPaymentView({
super.key,
this.planId,
this.payMonthly,
this.valueInCents,
});
final String? planId;
final bool? payMonthly;
final int? valueInCents;
@override
State<SelectPaymentView> createState() => _SelectPaymentViewState();
}
enum PaymentMethods {
twonlyCredit,
googleSubscription,
appleSubscription,
}
class _SelectPaymentViewState extends State<SelectPaymentView> {
int? ballanceInCents;
int checkoutInCents = 0;
bool tryAutoRenewal = true;
PaymentMethods paymentMethods = PaymentMethods.twonlyCredit;
@override
void initState() {
super.initState();
setCheckout(true);
initAsync();
}
Future initAsync() async {
final ballance = await loadPlanBallance();
ballanceInCents =
ballance!.transactions.map((a) => a.depositCents.toInt()).sum;
setState(() {});
}
void setCheckout(bool init) {
if (widget.valueInCents != null && widget.valueInCents! > 0) {
checkoutInCents = widget.valueInCents!;
} else if (widget.planId != null) {
checkoutInCents = getPlanPrice(widget.planId!, widget.payMonthly!);
} else {
/// Nothing to checkout for...
Navigator.pop(context);
}
if (!init) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
String totalPrice = (widget.planId != null && widget.payMonthly != null)
? "${localePrizing(context, checkoutInCents)}/${(widget.payMonthly!) ? context.lang.month : context.lang.year}"
: localePrizing(context, checkoutInCents);
bool canPay = (paymentMethods == PaymentMethods.twonlyCredit &&
(ballanceInCents == null || ballanceInCents! >= checkoutInCents));
return Scaffold(
appBar: AppBar(
title: Text(context.lang.selectPaymentMethode),
),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ListView(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.lang.twonlyCredit),
if (ballanceInCents != null)
Text(
"${context.lang.currentBalance}: ${localePrizing(context, ballanceInCents!)}",
style: TextStyle(fontSize: 10),
)
],
),
Checkbox(
value:
paymentMethods == PaymentMethods.twonlyCredit,
onChanged: (bool? value) {
setState(() {
paymentMethods = PaymentMethods.twonlyCredit;
});
},
),
],
),
),
),
),
],
),
),
if (!canPay) ...[
Padding(
padding: EdgeInsets.all(16),
child: Text(
context.lang.notEnoughCredit,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 12),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: FilledButton(
onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return VoucherView();
}));
initAsync();
},
child: Text(context.lang.chargeCredit),
),
),
],
Padding(
padding: const EdgeInsets.all(16.0),
child: ListTile(
title: Text(context.lang.autoRenewal),
subtitle: Text(context.lang.autoRenewalDesc),
onTap: () {
tryAutoRenewal = !tryAutoRenewal;
setCheckout(false);
},
trailing: Checkbox(
value: tryAutoRenewal,
onChanged: (a) {
tryAutoRenewal = !tryAutoRenewal;
setCheckout(false);
},
),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
context.lang.checkoutTotal,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
totalPrice,
textAlign: TextAlign.end,
),
],
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: FilledButton(
onPressed: (canPay)
? () async {
final res = await apiProvider.switchToPayedPlan(
widget.planId!, widget.payMonthly!, tryAutoRenewal);
if (!context.mounted) return;
if (res.isSuccess) {
context.read<CustomChangeProvider>().plan =
widget.planId!;
var user = await getUser();
if (user != null) {
user.subscriptionPlan = widget.planId!;
await updateUser(user);
}
context
.read<CustomChangeProvider>()
.updatePlan(widget.planId!);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(context.lang.planSuccessUpgraded)),
);
Navigator.of(context).pop(true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
errorCodeToText(context, res.error),
)),
);
}
}
: null,
child: Text(context.lang.checkoutSubmit),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => launchUrl(Uri.parse(
"https://twonly.eu/legal/de/#revocation-policy")),
child: Text(
"Widerrufsbelehrung",
style: TextStyle(color: Colors.blue),
),
),
TextButton(
onPressed: () => launchUrl(
Uri.parse("https://twonly.eu/legal/de/agb.html")),
child: Text(
"ABG",
style: TextStyle(color: Colors.blue),
),
)
],
),
SizedBox(height: 20)
],
),
),
);
}
}

View file

@ -10,9 +10,47 @@ import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/components/better_list_title.dart'; import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/views/settings/subscription/checkout_view.dart';
import 'package:twonly/src/views/settings/subscription/transaction_view.dart'; import 'package:twonly/src/views/settings/subscription/transaction_view.dart';
import 'package:twonly/src/views/settings/subscription/voucher_view.dart'; import 'package:twonly/src/views/settings/subscription/voucher_view.dart';
String localePrizing(BuildContext context, int cents) {
Locale myLocale = Localizations.localeOf(context);
double euros = cents / 100;
if (euros == euros.toInt()) {
return "${euros.toInt()}";
}
return NumberFormat.currency(
locale: myLocale.toString(),
symbol: '',
decimalDigits: 2,
).format(cents / 100);
}
Future<Response_PlanBallance?> loadPlanBallance() async {
Response_PlanBallance? ballance;
final user = await getUser();
if (user == null) return ballance;
ballance = await apiProvider.getPlanBallance();
if (ballance != null) {
user.lastPlanBallance = ballance.writeToJson();
await updateUser(user);
} else if (user.lastPlanBallance != null) {
try {
ballance = Response_PlanBallance.fromJson(
user.lastPlanBallance!,
);
} catch (e) {
Logger("subscription_view.dart").shout("from json: $e");
}
}
return ballance;
}
class SubscriptionView extends StatefulWidget { class SubscriptionView extends StatefulWidget {
const SubscriptionView({super.key}); const SubscriptionView({super.key});
@ -31,21 +69,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
} }
Future initAsync() async { Future initAsync() async {
final user = await getUser(); ballance = await loadPlanBallance();
if (user == null) return;
ballance = await apiProvider.getPlanBallance();
if (ballance != null) {
user.lastPlanBallance = ballance!.writeToJson();
await updateUser(user);
} else if (user.lastPlanBallance != null) {
try {
ballance = Response_PlanBallance.fromJson(
user.lastPlanBallance!,
);
} catch (e) {
Logger("subscription_view.dart").shout("from json: $e");
}
}
setState(() {}); setState(() {});
} }
@ -110,25 +134,25 @@ class _SubscriptionViewState extends State<SubscriptionView> {
), ),
if (currentPlan != "Family" && currentPlan != "Pro") if (currentPlan != "Family" && currentPlan != "Pro")
PlanCard( PlanCard(
title: "Pro", planId: "Pro",
yearlyPrice: context.lang.proYearlyPrice, onTap: () async {
monthlyPrice: context.lang.proMonthlyPrice, await Navigator.push(context,
features: [ MaterialPageRoute(builder: (context) {
context.lang.proFeature1, return CheckoutView(planId: "Pro");
context.lang.proFeature2, }));
context.lang.proFeature3, initAsync();
], },
), ),
if (currentPlan != "Family") if (currentPlan != "Family")
PlanCard( PlanCard(
title: "Family", planId: "Family",
yearlyPrice: context.lang.familyYearlyPrice, onTap: () async {
monthlyPrice: context.lang.familyMonthlyPrice, await Navigator.push(context,
features: [ MaterialPageRoute(builder: (context) {
context.lang.familyFeature1, return CheckoutView(planId: "Family");
context.lang.familyFeature2, }));
context.lang.familyFeature3, initAsync();
], },
), ),
if (currentPlan == "Preview" || currentPlan == "Free") ...[ if (currentPlan == "Preview" || currentPlan == "Free") ...[
SizedBox(height: 10), SizedBox(height: 10),
@ -145,31 +169,23 @@ class _SubscriptionViewState extends State<SubscriptionView> {
SizedBox(height: 10), SizedBox(height: 10),
if (currentPlan != "Free") if (currentPlan != "Free")
PlanCard( PlanCard(
title: "Free", planId: "Free",
yearlyPrice: "", onTap: () {},
monthlyPrice: "",
features: [
context.lang.freeFeature1,
],
), ),
PlanCard( PlanCard(
title: "Plus", planId: "Plus",
yearlyPrice: "", onTap: () {},
monthlyPrice: "",
features: [
context.lang.plusFeature1,
],
), ),
], ],
SizedBox(height: 10), SizedBox(height: 10),
if (currentPlan != "Family") Divider(), if (currentPlan != "Family") Divider(),
if (currentPlan == "Family" || currentPlan == "Pro") if (currentPlan == "Family" || currentPlan == "Pro")
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.userPlus, icon: FontAwesomeIcons.gears,
text: "Manage your subscription", text: context.lang.manageSubscription,
subtitle: (nextPayment != null) subtitle: (nextPayment != null)
? Text( ? Text(
"Next payment: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}") "${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}")
: null, : null,
onTap: () {}, onTap: () {},
), ),
@ -214,81 +230,118 @@ class _SubscriptionViewState extends State<SubscriptionView> {
} }
} }
int getPlanPrice(String planId, bool paidMonthly) {
switch (planId) {
case "Pro":
return (paidMonthly) ? 100 : 1000;
case "Family":
return (paidMonthly) ? 200 : 2000;
}
return 0;
}
class PlanCard extends StatelessWidget { class PlanCard extends StatelessWidget {
final String title; final String planId;
final String yearlyPrice; final Function()? onTap;
final String monthlyPrice;
final List<String> features;
const PlanCard({ const PlanCard({
super.key, super.key,
required this.title, required this.planId,
required this.yearlyPrice, this.onTap,
required this.monthlyPrice,
required this.features,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int yearlyPrice = getPlanPrice(planId, false);
int monthlyPrice = getPlanPrice(planId, true);
List<String> features = [];
switch (planId) {
case "Free":
features = [context.lang.freeFeature1];
break;
case "Plus":
features = [context.lang.plusFeature1];
break;
case "Pro":
features = [
context.lang.proFeature1,
context.lang.proFeature2,
context.lang.proFeature3
];
break;
case "Family":
features = [
context.lang.familyFeature1,
context.lang.familyFeature2,
context.lang.familyFeature3
];
break;
default:
}
return Padding( return Padding(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: Card( child: GestureDetector(
elevation: 4, onTap: onTap,
shape: RoundedRectangleBorder( child: Card(
borderRadius: BorderRadius.circular(10), elevation: 4,
), shape: RoundedRectangleBorder(
child: Padding( borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), ),
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
children: [ child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
title, children: [
textAlign: TextAlign.center, Text(
style: TextStyle( planId,
fontSize: 24, textAlign: TextAlign.center,
fontWeight: FontWeight.bold, style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (yearlyPrice != 0) SizedBox(height: 10),
if (yearlyPrice != 0)
Column(
children: [
Text(
"${localePrizing(context, yearlyPrice)}/${context.lang.year}",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
"${localePrizing(context, monthlyPrice)}/${context.lang.month}",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
)
],
),
SizedBox(height: 10),
...features.map(
(feature) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Text(
feature,
textAlign: TextAlign.center,
), ),
), ),
if (yearlyPrice != "") SizedBox(height: 10),
if (yearlyPrice != "")
Column(
children: [
Text(
yearlyPrice,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
monthlyPrice,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
)
],
),
SizedBox(height: 10),
...features.map(
(feature) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Text(
feature,
textAlign: TextAlign.center,
),
), ),
), ],
], ),
), ),
), ),
), ),

View file

@ -14,17 +14,6 @@ class TransactionView extends StatefulWidget {
} }
class _TransactionViewState extends State<TransactionView> { class _TransactionViewState extends State<TransactionView> {
// String typeToText(String type) {
// switch (type) {
// case "VoucherCreated":
// break;
// default:
// }
// }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(