diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 4c46a3a..227498c 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -169,13 +169,12 @@ "errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.", "errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.", "errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.", - "proYearlyPrice": "10€/Jahr", - "proMonthlyPrice": "1€/Monat", + "errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.", "proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "proFeature2": "1 zusätzlicher Plus Benutzer", "proFeature3": "3 zusätzliche kostenlose Benutzer", - "familyYearlyPrice": "20€/Jahr", - "familyMonthlyPrice": "2€/Monat", + "year": "year", + "month": "month", "familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "familyFeature2": "4 zusätzliche Plus Benutzer", "familyFeature3": "5 zusätzliche kostenlose Benutzer", @@ -183,7 +182,7 @@ "freeFeature1": "3 Medien-Datei-Uploads pro Tag", "plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", "transactionHistory": "Transaktionshistorie", - "currentBalance": "Aktueller Kontostand", + "currentBalance": "Dein Guthaben", "manageAdditionalUsers": "Zusätzlichen Benutzer verwalten", "open": "Offene", "buy": "Kaufen", @@ -203,5 +202,16 @@ "transactionThanksForTesting": "Danke fürs Testen", "transactionUnknown": "Unbekannte Transaktion", "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" } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 2ccf2be..325d698 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -326,14 +326,13 @@ "errorVoucherInvalid": "The voucher code you entered is not valid.", "errorPlanLimitReached": "You have reached your plans limit. Please upgrade your 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.", - "proYearlyPrice": "10€/year", - "proMonthlyPrice": "1€/month", + "year": "year", + "month": "month", "proFeature1": "✓ Unlimited media file uploads", "proFeature2": "1 additional Plus user", "proFeature3": "3 additional Free users", - "familyYearlyPrice": "20€/year", - "familyMonthlyPrice": "2€/month", "familyFeature1": "✓ Unlimited media file uploads", "familyFeature2": "4 additional Plus users", "familyFeature3": "5 additional Free users", @@ -341,6 +340,8 @@ "freeFeature1": "3 Media file uploads per day", "plusFeature1": "✓ Unlimited media file uploads", "transactionHistory": "Your transaction history", + "manageSubscription": "Manage your subscription", + "nextPayment": "Next payment", "currentBalance": "Current balance", "manageAdditionalUsers": "Manage your additional users", "open": "Open", @@ -361,5 +362,17 @@ "transactionThanksForTesting": "Thank you for testing", "transactionUnknown": "Unknown transaction", "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." } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 7b58881..30480a5 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1019,23 +1019,29 @@ abstract class AppLocalizations { /// **'This feature is not available in your current plan.'** 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. /// /// In en, this message translates to: /// **'Upgrade to a paid plan.'** String get upgradeToPaidPlan; - /// No description provided for @proYearlyPrice. + /// No description provided for @year. /// /// In en, this message translates to: - /// **'10€/year'** - String get proYearlyPrice; + /// **'year'** + String get year; - /// No description provided for @proMonthlyPrice. + /// No description provided for @month. /// /// In en, this message translates to: - /// **'1€/month'** - String get proMonthlyPrice; + /// **'month'** + String get month; /// No description provided for @proFeature1. /// @@ -1055,18 +1061,6 @@ abstract class AppLocalizations { /// **'3 additional Free users'** 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. /// /// In en, this message translates to: @@ -1109,6 +1103,18 @@ abstract class AppLocalizations { /// **'Your transaction history'** 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. /// /// In en, this message translates to: @@ -1234,6 +1240,72 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Voucher redeemed'** 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 { diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index d2cf65a..b611047 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -482,14 +482,17 @@ class AppLocalizationsDe extends AppLocalizations { @override 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 String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.'; @override - String get proYearlyPrice => '10€/Jahr'; + String get year => 'year'; @override - String get proMonthlyPrice => '1€/Monat'; + String get month => 'month'; @override String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; @@ -500,12 +503,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get proFeature3 => '3 zusätzliche kostenlose Benutzer'; - @override - String get familyYearlyPrice => '20€/Jahr'; - - @override - String get familyMonthlyPrice => '2€/Monat'; - @override String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; @@ -528,7 +525,13 @@ class AppLocalizationsDe extends AppLocalizations { String get transactionHistory => 'Transaktionshistorie'; @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 String get manageAdditionalUsers => 'Zusätzlichen Benutzer verwalten'; @@ -589,4 +592,37 @@ class AppLocalizationsDe extends AppLocalizations { @override 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'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 6caf01e..b959d5c 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -482,14 +482,17 @@ class AppLocalizationsEn extends AppLocalizations { @override 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 String get upgradeToPaidPlan => 'Upgrade to a paid plan.'; @override - String get proYearlyPrice => '10€/year'; + String get year => 'year'; @override - String get proMonthlyPrice => '1€/month'; + String get month => 'month'; @override String get proFeature1 => '✓ Unlimited media file uploads'; @@ -500,12 +503,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get proFeature3 => '3 additional Free users'; - @override - String get familyYearlyPrice => '20€/year'; - - @override - String get familyMonthlyPrice => '2€/month'; - @override String get familyFeature1 => '✓ Unlimited media file uploads'; @@ -527,6 +524,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get transactionHistory => 'Your transaction history'; + @override + String get manageSubscription => 'Manage your subscription'; + + @override + String get nextPayment => 'Next payment'; + @override String get currentBalance => 'Current balance'; @@ -589,4 +592,37 @@ class AppLocalizationsEn extends AppLocalizations { @override 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.'; } diff --git a/lib/src/model/protobuf/api/client_to_server.pb.dart b/lib/src/model/protobuf/api/client_to_server.pb.dart index 7c00faa..5bdb471 100644 --- a/lib/src/model/protobuf/api/client_to_server.pb.dart +++ b/lib/src/model/protobuf/api/client_to_server.pb.dart @@ -919,6 +919,7 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage { factory ApplicationData_SwitchToPayedPlan({ $core.String? planId, $core.bool? payMonthly, + $core.bool? autoRenewal, }) { final $result = create(); if (planId != null) { @@ -927,6 +928,9 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage { if (payMonthly != null) { $result.payMonthly = payMonthly; } + if (autoRenewal != null) { + $result.autoRenewal = autoRenewal; + } return $result; } 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) ..aOS(1, _omitFieldNames ? '' : 'planId') ..aOB(2, _omitFieldNames ? '' : 'payMonthly') + ..aOB(3, _omitFieldNames ? '' : 'autoRenewal') ..hasRequiredFields = false ; @@ -977,6 +982,15 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage { $core.bool hasPayMonthly() => $_has(1); @$pb.TagNumber(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 { diff --git a/lib/src/model/protobuf/api/client_to_server.pbjson.dart b/lib/src/model/protobuf/api/client_to_server.pbjson.dart index 0fd9875..1e84f19 100644 --- a/lib/src/model/protobuf/api/client_to_server.pbjson.dart +++ b/lib/src/model/protobuf/api/client_to_server.pbjson.dart @@ -211,6 +211,7 @@ const ApplicationData_SwitchToPayedPlan$json = { '2': [ {'1': 'plan_id', '3': 1, '4': 1, '5': 9, '10': 'planId'}, {'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' 'ASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgAS' 'gJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGikK' - 'DVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpNChFTd2l0Y2hUb1BheW' + 'DVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpwChFTd2l0Y2hUb1BheW' 'VkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5X21vbnRobHkYAiABKAhSCnBh' - 'eU1vbnRobHkaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW' - '50cxoNCgtHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFucxoX' - 'ChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZWRlZW' - '1BZGRpdGlvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRotChJHZXRQ' - 'cmVrZXlzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGjsKDkdldFVwbG9hZFRva2' - 'VuEikKEHJlY2lwaWVudHNfY291bnQYASABKA1SD3JlY2lwaWVudHNDb3VudBqJAQoKVXBsb2Fk' - 'RGF0YRIhCgx1cGxvYWRfdG9rZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDV' - 'IGb2Zmc2V0EhIKBGRhdGEYAyABKAxSBGRhdGESHwoIY2hlY2tzdW0YBCABKAxIAFIIY2hlY2tz' - 'dW2IAQFCCwoJX2NoZWNrc3VtGk0KDERvd25sb2FkRGF0YRIlCg5kb3dubG9hZF90b2tlbhgBIA' - 'EoDFINZG93bmxvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldEIRCg9BcHBsaWNhdGlv' - 'bkRhdGE='); + 'eU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUmVuZXdhbBowCg1DcmVhdGVWb3' + 'VjaGVyEh8KC3ZhbHVlX2NlbnRzGAEgASgNUgp2YWx1ZUNlbnRzGg0KC0dldExvY2F0aW9uGg0K' + 'C0dldFZvdWNoZXJzGhMKEUdldEF2YWlsYWJsZVBsYW5zGhcKFUdldEFkZEFjY291bnRzSW52aX' + 'RlcxoVChNHZXRDdXJyZW50UGxhbkluZm9zGjcKFFJlZGVlbUFkZGl0aW9uYWxDb2RlEh8KC2lu' + 'dml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2' + 'VyX2lkGAEgASgDUgZ1c2VySWQaOwoOR2V0VXBsb2FkVG9rZW4SKQoQcmVjaXBpZW50c19jb3Vu' + 'dBgBIAEoDVIPcmVjaXBpZW50c0NvdW50GokBCgpVcGxvYWREYXRhEiEKDHVwbG9hZF90b2tlbh' + 'gBIAEoDFILdXBsb2FkVG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXQSEgoEZGF0YRgDIAEo' + 'DFIEZGF0YRIfCghjaGVja3N1bRgEIAEoDEgAUghjaGVja3N1bYgBAUILCglfY2hlY2tzdW0aTQ' + 'oMRG93bmxvYWREYXRhEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuEhYK' + 'Bm9mZnNldBgCIAEoDVIGb2Zmc2V0QhEKD0FwcGxpY2F0aW9uRGF0YQ=='); @$core.Deprecated('Use responseDescriptor instead') const Response$json = { diff --git a/lib/src/model/protobuf/api/error.pbenum.dart b/lib/src/model/protobuf/api/error.pbenum.dart index 50ddfc2..4052c31 100644 --- a/lib/src/model/protobuf/api/error.pbenum.dart +++ b/lib/src/model/protobuf/api/error.pbenum.dart @@ -42,6 +42,7 @@ class ErrorCode extends $pb.ProtobufEnum { static const ErrorCode PlanLimitReached = ErrorCode._(1023, _omitEnumNames ? '' : 'PlanLimitReached'); static const ErrorCode NotEnoughCredit = ErrorCode._(1024, _omitEnumNames ? '' : 'NotEnoughCredit'); static const ErrorCode PlanDowngrade = ErrorCode._(1025, _omitEnumNames ? '' : 'PlanDowngrade'); + static const ErrorCode PlanUpgradeNotYearly = ErrorCode._(1026, _omitEnumNames ? '' : 'PlanUpgradeNotYearly'); static const $core.List values = [ Unknown, @@ -72,6 +73,7 @@ class ErrorCode extends $pb.ProtobufEnum { PlanLimitReached, NotEnoughCredit, PlanDowngrade, + PlanUpgradeNotYearly, ]; static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/lib/src/model/protobuf/api/error.pbjson.dart b/lib/src/model/protobuf/api/error.pbjson.dart index 19b9ee3..1923e25 100644 --- a/lib/src/model/protobuf/api/error.pbjson.dart +++ b/lib/src/model/protobuf/api/error.pbjson.dart @@ -45,6 +45,7 @@ const ErrorCode$json = { {'1': 'PlanLimitReached', '2': 1023}, {'1': 'NotEnoughCredit', '2': 1024}, {'1': 'PlanDowngrade', '2': 1025}, + {'1': 'PlanUpgradeNotYearly', '2': 1026}, ], }; @@ -62,5 +63,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode( '4Q+QcSGAoTQXBpRW5kcG9pbnROb3RGb3VuZBD6BxIWChFBdXRoVG9rZW5Ob3RWYWxpZBD7BxIT' 'Cg5JbnZhbGlkUHJlS2V5cxD8BxITCg5Wb3VjaGVySW5WYWxpZBD9BxITCg5QbGFuTm90QWxsb3' 'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q' - 'bGFuRG93bmdyYWRlEIEI'); + 'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIII'); diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index 200586c..bcda4c2 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -440,6 +440,17 @@ class ApiProvider { return await sendRequestSync(req); } + Future 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 redeemVoucher(String voucher) async { var get = ApplicationData_RedeemVoucher()..voucher = voucher; var appData = ApplicationData()..redeemvoucher = get; diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 0052af4..0efb2e9 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -85,6 +85,8 @@ String errorCodeToText(BuildContext context, ErrorCode code) { return context.lang.errorPlanNotAllowed; case ErrorCode.VoucherInValid: return context.lang.errorVoucherInvalid; + case ErrorCode.PlanUpgradeNotYearly: + return context.lang.errorPlanUpgradeNotYearly; default: return code.toString(); // Fallback for unrecognized keys } diff --git a/lib/src/views/settings/subscription/checkout_view.dart b/lib/src/views/settings/subscription/checkout_view.dart new file mode 100644 index 0000000..61d2a70 --- /dev/null +++ b/lib/src/views/settings/subscription/checkout_view.dart @@ -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 createState() => _CheckoutViewState(); +} + +class _CheckoutViewState extends State { + 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) + ], + ), + ), + ); + } +} diff --git a/lib/src/views/settings/subscription/select_payment.dart b/lib/src/views/settings/subscription/select_payment.dart new file mode 100644 index 0000000..b40af5e --- /dev/null +++ b/lib/src/views/settings/subscription/select_payment.dart @@ -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 createState() => _SelectPaymentViewState(); +} + +enum PaymentMethods { + twonlyCredit, + googleSubscription, + appleSubscription, +} + +class _SelectPaymentViewState extends State { + 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().plan = + widget.planId!; + var user = await getUser(); + if (user != null) { + user.subscriptionPlan = widget.planId!; + await updateUser(user); + } + context + .read() + .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) + ], + ), + ), + ); + } +} diff --git a/lib/src/views/settings/subscription/subscription_view.dart b/lib/src/views/settings/subscription/subscription_view.dart index c034155..6ca78dd 100644 --- a/lib/src/views/settings/subscription/subscription_view.dart +++ b/lib/src/views/settings/subscription/subscription_view.dart @@ -10,9 +10,47 @@ 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/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/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 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 { const SubscriptionView({super.key}); @@ -31,21 +69,7 @@ class _SubscriptionViewState extends State { } Future initAsync() async { - final user = await getUser(); - 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"); - } - } + ballance = await loadPlanBallance(); setState(() {}); } @@ -110,25 +134,25 @@ class _SubscriptionViewState extends State { ), if (currentPlan != "Family" && currentPlan != "Pro") PlanCard( - title: "Pro", - yearlyPrice: context.lang.proYearlyPrice, - monthlyPrice: context.lang.proMonthlyPrice, - features: [ - context.lang.proFeature1, - context.lang.proFeature2, - context.lang.proFeature3, - ], + planId: "Pro", + onTap: () async { + await Navigator.push(context, + MaterialPageRoute(builder: (context) { + return CheckoutView(planId: "Pro"); + })); + initAsync(); + }, ), if (currentPlan != "Family") PlanCard( - title: "Family", - yearlyPrice: context.lang.familyYearlyPrice, - monthlyPrice: context.lang.familyMonthlyPrice, - features: [ - context.lang.familyFeature1, - context.lang.familyFeature2, - context.lang.familyFeature3, - ], + planId: "Family", + onTap: () async { + await Navigator.push(context, + MaterialPageRoute(builder: (context) { + return CheckoutView(planId: "Family"); + })); + initAsync(); + }, ), if (currentPlan == "Preview" || currentPlan == "Free") ...[ SizedBox(height: 10), @@ -145,31 +169,23 @@ class _SubscriptionViewState extends State { SizedBox(height: 10), if (currentPlan != "Free") PlanCard( - title: "Free", - yearlyPrice: "", - monthlyPrice: "", - features: [ - context.lang.freeFeature1, - ], + planId: "Free", + onTap: () {}, ), PlanCard( - title: "Plus", - yearlyPrice: "", - monthlyPrice: "", - features: [ - context.lang.plusFeature1, - ], + planId: "Plus", + onTap: () {}, ), ], SizedBox(height: 10), if (currentPlan != "Family") Divider(), if (currentPlan == "Family" || currentPlan == "Pro") BetterListTile( - icon: FontAwesomeIcons.userPlus, - text: "Manage your subscription", + icon: FontAwesomeIcons.gears, + text: context.lang.manageSubscription, subtitle: (nextPayment != null) ? Text( - "Next payment: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}") + "${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}") : null, onTap: () {}, ), @@ -214,81 +230,118 @@ class _SubscriptionViewState extends State { } } +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 { - final String title; - final String yearlyPrice; - final String monthlyPrice; - final List features; + final String planId; + final Function()? onTap; const PlanCard({ super.key, - required this.title, - required this.yearlyPrice, - required this.monthlyPrice, - required this.features, + required this.planId, + this.onTap, }); @override Widget build(BuildContext context) { + int yearlyPrice = getPlanPrice(planId, false); + int monthlyPrice = getPlanPrice(planId, true); + List 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( padding: const EdgeInsets.only(left: 16, right: 16), - child: Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, + child: GestureDetector( + onTap: onTap, + child: Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + planId, + textAlign: TextAlign.center, + 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, - ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/src/views/settings/subscription/transaction_view.dart b/lib/src/views/settings/subscription/transaction_view.dart index a3dacae..8834af6 100644 --- a/lib/src/views/settings/subscription/transaction_view.dart +++ b/lib/src/views/settings/subscription/transaction_view.dart @@ -14,17 +14,6 @@ class TransactionView extends StatefulWidget { } class _TransactionViewState extends State { - // String typeToText(String type) { - - // switch (type) { - // case "VoucherCreated": - - // break; - // default: - // } - - // } - @override Widget build(BuildContext context) { return Scaffold(