mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +00:00
vouchers work
This commit is contained in:
parent
b5a8e785ee
commit
f83cc4ace6
13 changed files with 1020 additions and 162 deletions
|
|
@ -41,20 +41,25 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
// register global callbacks to the widget tree
|
// register global callbacks to the widget tree
|
||||||
globalCallbackConnectionState = (update) {
|
globalCallbackConnectionState = (update) {
|
||||||
context.read<CustomChangeProvider>().updateConnectionState(update);
|
context.read<CustomChangeProvider>().updateConnectionState(update);
|
||||||
|
setUserPlan();
|
||||||
setupNotificationWithUsers();
|
setupNotificationWithUsers();
|
||||||
};
|
};
|
||||||
|
|
||||||
initAsync();
|
initAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future initAsync() async {
|
Future setUserPlan() async {
|
||||||
apiProvider.connect();
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null && context.mounted) {
|
if (user != null && context.mounted) {
|
||||||
context.read<CustomChangeProvider>().updatePlan(user.subscriptionPlan);
|
context.read<CustomChangeProvider>().updatePlan(user.subscriptionPlan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future initAsync() async {
|
||||||
|
setUserPlan();
|
||||||
|
apiProvider.connect();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@
|
||||||
"onboardingBuyOneGetTwoTitle": "Kaufe eins, bekomme zwei",
|
"onboardingBuyOneGetTwoTitle": "Kaufe eins, bekomme zwei",
|
||||||
"onboardingBuyOneGetTwoBody": "twonly benötigt immer mindestens zwei Personen, daher erhältst du beim Kauf eine zweite kostenlose Lizenz für deinen twonly-Partner.",
|
"onboardingBuyOneGetTwoBody": "twonly benötigt immer mindestens zwei Personen, daher erhältst du beim Kauf eine zweite kostenlose Lizenz für deinen twonly-Partner.",
|
||||||
"onboardingGetStartedTitle": "Auf geht's",
|
"onboardingGetStartedTitle": "Auf geht's",
|
||||||
"onboardingGetStartedBody": "Du kannst twonly 14 Tage lang kostenlos testen, danach kostet es entweder 1€/Monat oder 9€/Jahr.",
|
"onboardingGetStartedBody": "Du kannst twonly kostenlos im Preview-Modus testen. In diesem Modus kannst du von anderen gefunden werden und Bilder oder Videos empfangen, aber du kannst selbst keine senden.",
|
||||||
"onboardingTryForFree": "Kostenlos testen",
|
"onboardingTryForFree": "Kostenlos testen",
|
||||||
"registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!",
|
"registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!",
|
||||||
"registerUsernameDecoration": "Benutzername",
|
"registerUsernameDecoration": "Benutzername",
|
||||||
"registerUsernameLimits": "Der Benutzername muss 3 bis 12 Zeichen lang sein und darf nur aus Buchstaben (a-z) und Zahlen (0-9) bestehen.",
|
"registerUsernameLimits": "Der Benutzername muss 3 bis 12 Zeichen lang sein und darf nur aus Buchstaben (a-z) und Zahlen (0-9) bestehen.",
|
||||||
"registerSubmitButton": "Jetzt registrieren!",
|
"registerSubmitButton": "Jetzt registrieren!",
|
||||||
|
"registerTwonlyCodeText": "Hast du einen twonly-Code erhalten? Dann löse ihn entweder direkt hier oder später ein!",
|
||||||
|
"registerTwonlyCodeLabel": "twonly-Code",
|
||||||
"newMessageTitle": "Neue Nachricht",
|
"newMessageTitle": "Neue Nachricht",
|
||||||
"chatsTapToSend": "Klicke, um dein erstes Bild zu teilen.",
|
"chatsTapToSend": "Klicke, um dein erstes Bild zu teilen.",
|
||||||
"cameraPreviewSendTo": "Senden an",
|
"cameraPreviewSendTo": "Senden an",
|
||||||
|
|
@ -161,5 +163,38 @@
|
||||||
"errorInvalidPublicKey": "Der von dir angegebene öffentliche Schlüssel ist ungültig. Bitte überprüfe den Schlüssel und versuche es erneut.",
|
"errorInvalidPublicKey": "Der von dir angegebene öffentliche Schlüssel ist ungültig. Bitte überprüfe den Schlüssel und versuche es erneut.",
|
||||||
"errorSessionAlreadyAuthenticated": "Du bist bereits angemeldet. Bitte melde dich ab, wenn du dich mit einem anderen Konto anmelden möchtest.",
|
"errorSessionAlreadyAuthenticated": "Du bist bereits angemeldet. Bitte melde dich ab, wenn du dich mit einem anderen Konto anmelden möchtest.",
|
||||||
"errorSessionNotAuthenticated": "Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.",
|
"errorSessionNotAuthenticated": "Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.",
|
||||||
"errorOnlyOneSessionAllowed": "Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren."
|
"errorOnlyOneSessionAllowed": "Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.",
|
||||||
|
"upgradeToPaidPlan": "Upgrade auf einen kostenpflichtigen Plan.",
|
||||||
|
"errorNotEnoughCredit": "Du hast nicht genügend twonly-Guthaben.",
|
||||||
|
"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",
|
||||||
|
"proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
|
"proFeature2": "1 zusätzlicher Plus Benutzer",
|
||||||
|
"proFeature3": "3 zusätzliche kostenlose Benutzer",
|
||||||
|
"familyYearlyPrice": "20€/Jahr",
|
||||||
|
"familyMonthlyPrice": "2€/Monat",
|
||||||
|
"familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
|
"familyFeature2": "4 zusätzliche Plus Benutzer",
|
||||||
|
"familyFeature3": "5 zusätzliche kostenlose Benutzer",
|
||||||
|
"redeemUserInviteCode": "Oder löse einen zusätzlichen twonly-Code ein.",
|
||||||
|
"freeFeature1": "3 Medien-Datei-Uploads pro Tag",
|
||||||
|
"plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
|
"transactionHistory": "Transaktionshistorie",
|
||||||
|
"currentBalance": "Aktueller Kontostand",
|
||||||
|
"manageAdditionalUsers": "Zusätzlichen Benutzer verwalten",
|
||||||
|
"open": "Offene",
|
||||||
|
"buy": "Kaufen",
|
||||||
|
"createOrRedeemVoucher": "Gutschein erstellen oder einlösen",
|
||||||
|
"createVoucher": "Gutschein kaufen",
|
||||||
|
"createVoucherDesc": "Wähle den Wert des Gutscheins. Der Wert des Gutschein wird von deinem twonly-Guthaben abgezogen.",
|
||||||
|
"redeemVoucher": "Gutschein einlösen",
|
||||||
|
"voucherCreated": "Gutschein wurde erstellt",
|
||||||
|
"openVouchers": "Offene Gutscheine",
|
||||||
|
"enterVoucherCode": "Gutschein Code eingeben",
|
||||||
|
"voucherRedeemed": "Gutschein eingelöst",
|
||||||
|
"requestedVouchers": "Beantragte Gutscheine",
|
||||||
|
"redeemedVouchers": "Eingelöste Gutscheine"
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"@onboardingBuyOneGetTwoBody": {},
|
"@onboardingBuyOneGetTwoBody": {},
|
||||||
"onboardingGetStartedTitle": "Let's go!",
|
"onboardingGetStartedTitle": "Let's go!",
|
||||||
"@onboardingGetStartedTitle": {},
|
"@onboardingGetStartedTitle": {},
|
||||||
"onboardingGetStartedBody": "You can test twonly free of charge for 14 days, after that it costs either 1€/month or 9€/year.",
|
"onboardingGetStartedBody": "You can test twonly free of charge in preview mode. In this mode you can be found by others and receive pictures or videos but you cannot send any yourself.",
|
||||||
"@onboardingGetStartedBody": {},
|
"@onboardingGetStartedBody": {},
|
||||||
"onboardingTryForFree": "Try for free",
|
"onboardingTryForFree": "Try for free",
|
||||||
"@onboardingTryForFree": {},
|
"@onboardingTryForFree": {},
|
||||||
|
|
@ -42,6 +42,8 @@
|
||||||
"@registerUsernameLimits": {},
|
"@registerUsernameLimits": {},
|
||||||
"registerSubmitButton": "Register now!",
|
"registerSubmitButton": "Register now!",
|
||||||
"@registerSubmitButton": {},
|
"@registerSubmitButton": {},
|
||||||
|
"registerTwonlyCodeText": "Have you received a twonly code? Then redeem it either directly here or later!",
|
||||||
|
"registerTwonlyCodeLabel": "twonly-Code",
|
||||||
"newMessageTitle": "New message",
|
"newMessageTitle": "New message",
|
||||||
"@newMessageTitle": {},
|
"@newMessageTitle": {},
|
||||||
"chatsTapToSend": "Click to send your first image",
|
"chatsTapToSend": "Click to send your first image",
|
||||||
|
|
@ -319,5 +321,38 @@
|
||||||
"errorSessionNotAuthenticated": "Your session is not authenticated. Please log in to continue.",
|
"errorSessionNotAuthenticated": "Your session is not authenticated. Please log in to continue.",
|
||||||
"@errorSessionNotAuthenticated": {},
|
"@errorSessionNotAuthenticated": {},
|
||||||
"errorOnlyOneSessionAllowed": "Only one active session is allowed per user. Please log out from other devices to continue.",
|
"errorOnlyOneSessionAllowed": "Only one active session is allowed per user. Please log out from other devices to continue.",
|
||||||
"@errorOnlyOneSessionAllowed": {}
|
"@errorOnlyOneSessionAllowed": {},
|
||||||
|
"errorNotEnoughCredit": "You do not have enough twonly-credit.",
|
||||||
|
"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.",
|
||||||
|
"upgradeToPaidPlan": "Upgrade to a paid plan.",
|
||||||
|
"proYearlyPrice": "10€/year",
|
||||||
|
"proMonthlyPrice": "1€/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",
|
||||||
|
"redeemUserInviteCode": "Or redeem an additional user invite code.",
|
||||||
|
"freeFeature1": "3 Media file uploads per day",
|
||||||
|
"plusFeature1": "✓ Unlimited media file uploads",
|
||||||
|
"transactionHistory": "Your transaction history",
|
||||||
|
"currentBalance": "Current balance",
|
||||||
|
"manageAdditionalUsers": "Manage your additional users",
|
||||||
|
"open": "Open",
|
||||||
|
"createOrRedeemVoucher": "Buy or redeem voucher",
|
||||||
|
"createVoucher": "Buy voucher",
|
||||||
|
"createVoucherDesc": "Choose the value of the voucher. The value of the voucher will be deducted from your twonly balance.",
|
||||||
|
"redeemVoucher": "Redeem voucher",
|
||||||
|
"openVouchers": "Open vouchers",
|
||||||
|
"voucherCreated": "Voucher created",
|
||||||
|
"voucherRedeemed": "Voucher redeemed",
|
||||||
|
"enterVoucherCode": "Enter Voucher Code",
|
||||||
|
"requestedVouchers": "Requested vouchers",
|
||||||
|
"redeemedVouchers": "Redeemed vouchers",
|
||||||
|
"buy": "Buy"
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @onboardingGetStartedBody.
|
/// No description provided for @onboardingGetStartedBody.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'You can test twonly free of charge for 14 days, after that it costs either 1€/month or 9€/year.'**
|
/// **'You can test twonly free of charge in preview mode. In this mode you can be found by others and receive pictures or videos but you cannot send any yourself.'**
|
||||||
String get onboardingGetStartedBody;
|
String get onboardingGetStartedBody;
|
||||||
|
|
||||||
/// No description provided for @onboardingTryForFree.
|
/// No description provided for @onboardingTryForFree.
|
||||||
|
|
@ -221,6 +221,18 @@ abstract class AppLocalizations {
|
||||||
/// **'Register now!'**
|
/// **'Register now!'**
|
||||||
String get registerSubmitButton;
|
String get registerSubmitButton;
|
||||||
|
|
||||||
|
/// No description provided for @registerTwonlyCodeText.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Have you received a twonly code? Then redeem it either directly here or later!'**
|
||||||
|
String get registerTwonlyCodeText;
|
||||||
|
|
||||||
|
/// No description provided for @registerTwonlyCodeLabel.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'twonly-Code'**
|
||||||
|
String get registerTwonlyCodeLabel;
|
||||||
|
|
||||||
/// No description provided for @newMessageTitle.
|
/// No description provided for @newMessageTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -982,6 +994,204 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Only one active session is allowed per user. Please log out from other devices to continue.'**
|
/// **'Only one active session is allowed per user. Please log out from other devices to continue.'**
|
||||||
String get errorOnlyOneSessionAllowed;
|
String get errorOnlyOneSessionAllowed;
|
||||||
|
|
||||||
|
/// No description provided for @errorNotEnoughCredit.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You do not have enough twonly-credit.'**
|
||||||
|
String get errorNotEnoughCredit;
|
||||||
|
|
||||||
|
/// No description provided for @errorVoucherInvalid.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The voucher code you entered is not valid.'**
|
||||||
|
String get errorVoucherInvalid;
|
||||||
|
|
||||||
|
/// No description provided for @errorPlanLimitReached.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You have reached your plans limit. Please upgrade your plan.'**
|
||||||
|
String get errorPlanLimitReached;
|
||||||
|
|
||||||
|
/// No description provided for @errorPlanNotAllowed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This feature is not available in your current plan.'**
|
||||||
|
String get errorPlanNotAllowed;
|
||||||
|
|
||||||
|
/// No description provided for @upgradeToPaidPlan.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Upgrade to a paid plan.'**
|
||||||
|
String get upgradeToPaidPlan;
|
||||||
|
|
||||||
|
/// No description provided for @proYearlyPrice.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'10€/year'**
|
||||||
|
String get proYearlyPrice;
|
||||||
|
|
||||||
|
/// No description provided for @proMonthlyPrice.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'1€/month'**
|
||||||
|
String get proMonthlyPrice;
|
||||||
|
|
||||||
|
/// No description provided for @proFeature1.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'✓ Unlimited media file uploads'**
|
||||||
|
String get proFeature1;
|
||||||
|
|
||||||
|
/// No description provided for @proFeature2.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'1 additional Plus user'**
|
||||||
|
String get proFeature2;
|
||||||
|
|
||||||
|
/// No description provided for @proFeature3.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'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:
|
||||||
|
/// **'✓ Unlimited media file uploads'**
|
||||||
|
String get familyFeature1;
|
||||||
|
|
||||||
|
/// No description provided for @familyFeature2.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'4 additional Plus users'**
|
||||||
|
String get familyFeature2;
|
||||||
|
|
||||||
|
/// No description provided for @familyFeature3.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'5 additional Free users'**
|
||||||
|
String get familyFeature3;
|
||||||
|
|
||||||
|
/// No description provided for @redeemUserInviteCode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Or redeem an additional user invite code.'**
|
||||||
|
String get redeemUserInviteCode;
|
||||||
|
|
||||||
|
/// No description provided for @freeFeature1.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'3 Media file uploads per day'**
|
||||||
|
String get freeFeature1;
|
||||||
|
|
||||||
|
/// No description provided for @plusFeature1.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'✓ Unlimited media file uploads'**
|
||||||
|
String get plusFeature1;
|
||||||
|
|
||||||
|
/// No description provided for @transactionHistory.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Your transaction history'**
|
||||||
|
String get transactionHistory;
|
||||||
|
|
||||||
|
/// No description provided for @currentBalance.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Current balance'**
|
||||||
|
String get currentBalance;
|
||||||
|
|
||||||
|
/// No description provided for @manageAdditionalUsers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Manage your additional users'**
|
||||||
|
String get manageAdditionalUsers;
|
||||||
|
|
||||||
|
/// No description provided for @open.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Open'**
|
||||||
|
String get open;
|
||||||
|
|
||||||
|
/// No description provided for @createOrRedeemVoucher.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Buy or redeem voucher'**
|
||||||
|
String get createOrRedeemVoucher;
|
||||||
|
|
||||||
|
/// No description provided for @createVoucher.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Buy voucher'**
|
||||||
|
String get createVoucher;
|
||||||
|
|
||||||
|
/// No description provided for @createVoucherDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Choose the value of the voucher. The value of the voucher will be deducted from your twonly balance.'**
|
||||||
|
String get createVoucherDesc;
|
||||||
|
|
||||||
|
/// No description provided for @redeemVoucher.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Redeem voucher'**
|
||||||
|
String get redeemVoucher;
|
||||||
|
|
||||||
|
/// No description provided for @openVouchers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Open vouchers'**
|
||||||
|
String get openVouchers;
|
||||||
|
|
||||||
|
/// No description provided for @voucherCreated.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Voucher created'**
|
||||||
|
String get voucherCreated;
|
||||||
|
|
||||||
|
/// No description provided for @voucherRedeemed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Voucher redeemed'**
|
||||||
|
String get voucherRedeemed;
|
||||||
|
|
||||||
|
/// No description provided for @enterVoucherCode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Enter Voucher Code'**
|
||||||
|
String get enterVoucherCode;
|
||||||
|
|
||||||
|
/// No description provided for @requestedVouchers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Requested vouchers'**
|
||||||
|
String get requestedVouchers;
|
||||||
|
|
||||||
|
/// No description provided for @redeemedVouchers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Redeemed vouchers'**
|
||||||
|
String get redeemedVouchers;
|
||||||
|
|
||||||
|
/// No description provided for @buy.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Buy'**
|
||||||
|
String get buy;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get onboardingGetStartedTitle => 'Auf geht\'s';
|
String get onboardingGetStartedTitle => 'Auf geht\'s';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onboardingGetStartedBody => 'Du kannst twonly 14 Tage lang kostenlos testen, danach kostet es entweder 1€/Monat oder 9€/Jahr.';
|
String get onboardingGetStartedBody => 'Du kannst twonly kostenlos im Preview-Modus testen. In diesem Modus kannst du von anderen gefunden werden und Bilder oder Videos empfangen, aber du kannst selbst keine senden.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onboardingTryForFree => 'Kostenlos testen';
|
String get onboardingTryForFree => 'Kostenlos testen';
|
||||||
|
|
@ -71,6 +71,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get registerSubmitButton => 'Jetzt registrieren!';
|
String get registerSubmitButton => 'Jetzt registrieren!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerTwonlyCodeText => 'Hast du einen twonly-Code erhalten? Dann löse ihn entweder direkt hier oder später ein!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerTwonlyCodeLabel => 'twonly-Code';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get newMessageTitle => 'Neue Nachricht';
|
String get newMessageTitle => 'Neue Nachricht';
|
||||||
|
|
||||||
|
|
@ -463,4 +469,103 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get errorOnlyOneSessionAllowed => 'Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.';
|
String get errorOnlyOneSessionAllowed => 'Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorNotEnoughCredit => 'Du hast nicht genügend twonly-Guthaben.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorVoucherInvalid => 'Der eingegebene Gutschein-Code ist nicht gültig.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorPlanLimitReached => 'Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorPlanNotAllowed => 'Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proYearlyPrice => '10€/Jahr';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proMonthlyPrice => '1€/Monat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
||||||
|
|
||||||
|
@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';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature3 => '5 zusätzliche kostenlose Benutzer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemUserInviteCode => 'Oder löse einen zusätzlichen twonly-Code ein.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get freeFeature1 => '3 Medien-Datei-Uploads pro Tag';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plusFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get transactionHistory => 'Transaktionshistorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get currentBalance => 'Aktueller Kontostand';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manageAdditionalUsers => 'Zusätzlichen Benutzer verwalten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get open => 'Offene';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createOrRedeemVoucher => 'Gutschein erstellen oder einlösen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createVoucher => 'Gutschein kaufen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createVoucherDesc => 'Wähle den Wert des Gutscheins. Der Wert des Gutschein wird von deinem twonly-Guthaben abgezogen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemVoucher => 'Gutschein einlösen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openVouchers => 'Offene Gutscheine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get voucherCreated => 'Gutschein wurde erstellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get voucherRedeemed => 'Gutschein eingelöst';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterVoucherCode => 'Gutschein Code eingeben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get requestedVouchers => 'Beantragte Gutscheine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemedVouchers => 'Eingelöste Gutscheine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get buy => 'Kaufen';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get onboardingGetStartedTitle => 'Let\'s go!';
|
String get onboardingGetStartedTitle => 'Let\'s go!';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onboardingGetStartedBody => 'You can test twonly free of charge for 14 days, after that it costs either 1€/month or 9€/year.';
|
String get onboardingGetStartedBody => 'You can test twonly free of charge in preview mode. In this mode you can be found by others and receive pictures or videos but you cannot send any yourself.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get onboardingTryForFree => 'Try for free';
|
String get onboardingTryForFree => 'Try for free';
|
||||||
|
|
@ -71,6 +71,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get registerSubmitButton => 'Register now!';
|
String get registerSubmitButton => 'Register now!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerTwonlyCodeText => 'Have you received a twonly code? Then redeem it either directly here or later!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerTwonlyCodeLabel => 'twonly-Code';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get newMessageTitle => 'New message';
|
String get newMessageTitle => 'New message';
|
||||||
|
|
||||||
|
|
@ -463,4 +469,103 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get errorOnlyOneSessionAllowed => 'Only one active session is allowed per user. Please log out from other devices to continue.';
|
String get errorOnlyOneSessionAllowed => 'Only one active session is allowed per user. Please log out from other devices to continue.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorNotEnoughCredit => 'You do not have enough twonly-credit.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorVoucherInvalid => 'The voucher code you entered is not valid.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorPlanLimitReached => 'You have reached your plans limit. Please upgrade your plan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorPlanNotAllowed => 'This feature is not available in your current plan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get upgradeToPaidPlan => 'Upgrade to a paid plan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proYearlyPrice => '10€/year';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proMonthlyPrice => '1€/month';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get proFeature2 => '1 additional Plus user';
|
||||||
|
|
||||||
|
@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';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature2 => '4 additional Plus users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature3 => '5 additional Free users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemUserInviteCode => 'Or redeem an additional user invite code.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get freeFeature1 => '3 Media file uploads per day';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get plusFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get transactionHistory => 'Your transaction history';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get currentBalance => 'Current balance';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manageAdditionalUsers => 'Manage your additional users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get open => 'Open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createOrRedeemVoucher => 'Buy or redeem voucher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createVoucher => 'Buy voucher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createVoucherDesc => 'Choose the value of the voucher. The value of the voucher will be deducted from your twonly balance.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemVoucher => 'Redeem voucher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openVouchers => 'Open vouchers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get voucherCreated => 'Voucher created';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get voucherRedeemed => 'Voucher redeemed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterVoucherCode => 'Enter Voucher Code';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get requestedVouchers => 'Requested vouchers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get redeemedVouchers => 'Redeemed vouchers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get buy => 'Buy';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -419,6 +419,34 @@ class ApiProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Response_Vouchers?> getVoucherList() async {
|
||||||
|
var get = ApplicationData_GetVouchers();
|
||||||
|
var appData = ApplicationData()..getvouchers = get;
|
||||||
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
|
Result res = await sendRequestSync(req);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
server.Response_Ok ok = res.value;
|
||||||
|
if (ok.hasVouchers()) {
|
||||||
|
return ok.vouchers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result> buyVoucher(int valueInCents) async {
|
||||||
|
var get = ApplicationData_CreateVoucher()..valueCents = valueInCents;
|
||||||
|
var appData = ApplicationData()..createvoucher = get;
|
||||||
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
|
return await sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result> redeemVoucher(String voucher) async {
|
||||||
|
var get = ApplicationData_RedeemVoucher()..voucher = voucher;
|
||||||
|
var appData = ApplicationData()..redeemvoucher = get;
|
||||||
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
|
return await sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> updateFCMToken(String googleFcm) async {
|
Future<Result> updateFCMToken(String googleFcm) async {
|
||||||
var get = ApplicationData_UpdateGoogleFcmToken()..googleFcm = googleFcm;
|
var get = ApplicationData_UpdateGoogleFcmToken()..googleFcm = googleFcm;
|
||||||
var appData = ApplicationData()..updategooglefcmtoken = get;
|
var appData = ApplicationData()..updategooglefcmtoken = get;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
String plan = "";
|
String plan = "Preview";
|
||||||
Future<void> updateConnectionState(bool update) async {
|
Future<void> updateConnectionState(bool update) async {
|
||||||
_isConnected = update;
|
_isConnected = update;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -68,33 +68,23 @@ Uint8List getRandomUint8List(int length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String errorCodeToText(BuildContext context, ErrorCode code) {
|
String errorCodeToText(BuildContext context, ErrorCode code) {
|
||||||
switch (code.toString()) {
|
switch (code) {
|
||||||
case "Unknown":
|
case ErrorCode.InternalError:
|
||||||
return context.lang.errorUnknown;
|
|
||||||
case "BadRequest":
|
|
||||||
return context.lang.errorBadRequest;
|
|
||||||
case "TooManyRequests":
|
|
||||||
return context.lang.errorTooManyRequests;
|
|
||||||
case "InternalError":
|
|
||||||
return context.lang.errorInternalError;
|
return context.lang.errorInternalError;
|
||||||
case "InvalidInvitationCode":
|
case ErrorCode.InvalidInvitationCode:
|
||||||
return context.lang.errorInvalidInvitationCode;
|
return context.lang.errorInvalidInvitationCode;
|
||||||
case "UsernameAlreadyTaken":
|
case ErrorCode.UsernameAlreadyTaken:
|
||||||
return context.lang.errorUsernameAlreadyTaken;
|
return context.lang.errorUsernameAlreadyTaken;
|
||||||
case "SignatureNotValid":
|
case ErrorCode.UsernameNotValid:
|
||||||
return context.lang.errorSignatureNotValid;
|
|
||||||
case "UsernameNotFound":
|
|
||||||
return context.lang.errorUsernameNotFound;
|
|
||||||
case "UsernameNotValid":
|
|
||||||
return context.lang.errorUsernameNotValid;
|
return context.lang.errorUsernameNotValid;
|
||||||
case "InvalidPublicKey":
|
case ErrorCode.NotEnoughCredit:
|
||||||
return context.lang.errorInvalidPublicKey;
|
return context.lang.errorNotEnoughCredit;
|
||||||
case "SessionAlreadyAuthenticated":
|
case ErrorCode.PlanLimitReached:
|
||||||
return context.lang.errorSessionAlreadyAuthenticated;
|
return context.lang.errorPlanLimitReached;
|
||||||
case "SessionNotAuthenticated":
|
case ErrorCode.PlanNotAllowed:
|
||||||
return context.lang.errorSessionNotAuthenticated;
|
return context.lang.errorPlanNotAllowed;
|
||||||
case "OnlyOneSessionAllowed":
|
case ErrorCode.VoucherInValid:
|
||||||
return context.lang.errorOnlyOneSessionAllowed;
|
return context.lang.errorVoucherInvalid;
|
||||||
default:
|
default:
|
||||||
return code.toString(); // Fallback for unrecognized keys
|
return code.toString(); // Fallback for unrecognized keys
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
// Slide 1: Welcome to [App Name]
|
|
||||||
// Text: "Experience a new way to connect with friends through secure, spontaneous image sharing."
|
|
||||||
// Image Idea: A vibrant, welcoming graphic featuring diverse groups of friends using the app in various settings (e.g., at a café, at a party, etc.).
|
|
||||||
|
|
||||||
// Slide 2: End-to-End Encryption
|
|
||||||
// Text: "Your privacy matters. Enjoy peace of mind with end-to-end encryption, ensuring only you and your friends can see your images."
|
|
||||||
// Image Idea: A lock symbol overlaying a smartphone screen displaying an encrypted message, symbolizing security and privacy.
|
|
||||||
|
|
||||||
// Slide 3: Local Processing
|
|
||||||
// Text: "Everything is done locally. Our servers only see encrypted bytes, keeping your data safe from prying eyes."
|
|
||||||
// Image Idea: A visual representation of local processing, such as a smartphone with a shield icon, indicating that data remains on the device.
|
|
||||||
|
|
||||||
// Slide 4: Focus on Images
|
|
||||||
// Text: "Say goodbye to clutter! Our app is designed for sharing images, not useless distractions."
|
|
||||||
// Image Idea: A clean, minimalist interface showcasing a user effortlessly sending an image, with a focus on the image itself.
|
|
||||||
|
|
||||||
class OnboardingView extends StatelessWidget {
|
class OnboardingView extends StatelessWidget {
|
||||||
const OnboardingView({super.key, required this.callbackOnSuccess});
|
const OnboardingView({super.key, required this.callbackOnSuccess});
|
||||||
final Function callbackOnSuccess;
|
final Function callbackOnSuccess;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
|
|
||||||
final res = await apiProvider.register(username, inviteCode);
|
final res = await apiProvider.register(username, inviteCode);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isTryingToRegister = false;
|
||||||
|
});
|
||||||
|
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
Logger("create_new_user").info("Got user_id ${res.value} from server");
|
Logger("create_new_user").info("Got user_id ${res.value} from server");
|
||||||
final userData = UserData(
|
final userData = UserData(
|
||||||
|
|
@ -46,13 +50,6 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
subscriptionPlan: "Preview",
|
subscriptionPlan: "Preview",
|
||||||
);
|
);
|
||||||
storage.write(key: "userData", value: jsonEncode(userData));
|
storage.write(key: "userData", value: jsonEncode(userData));
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_isTryingToRegister = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.isSuccess) {
|
|
||||||
apiProvider.authenticate();
|
apiProvider.authenticate();
|
||||||
widget.callbackOnSuccess();
|
widget.callbackOnSuccess();
|
||||||
return;
|
return;
|
||||||
|
|
@ -130,31 +127,24 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
child: Text(
|
child: Text(
|
||||||
context.lang.registerUsernameLimits,
|
context.lang.registerUsernameLimits,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 7),
|
style: TextStyle(fontSize: 9),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// const SizedBox(height: 15),
|
const SizedBox(height: 20),
|
||||||
// Center(
|
Center(
|
||||||
// child: Text(
|
child: Text(
|
||||||
// "To protect this small experimental project you need an invitation code! To get one just ask the right person!",
|
context.lang.registerTwonlyCodeText,
|
||||||
// textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// TextField(
|
TextField(
|
||||||
// controller: inviteCodeController,
|
controller: inviteCodeController,
|
||||||
// decoration: getInputDecoration("Voucher code")),
|
decoration:
|
||||||
// const SizedBox(height: 25),
|
getInputDecoration(context.lang.registerTwonlyCodeLabel),
|
||||||
// Center(
|
),
|
||||||
// child: Text(
|
const SizedBox(height: 30),
|
||||||
// "Please ",
|
|
||||||
// textAlign: TextAlign.center,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
const SizedBox(height: 50),
|
|
||||||
// Padding(
|
|
||||||
// padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
Column(children: [
|
Column(children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: _isTryingToRegister
|
icon: _isTryingToRegister
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
@ -7,6 +8,7 @@ import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
||||||
import 'package:twonly/src/providers/connection_provider.dart';
|
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/views/components/better_list_title.dart';
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
|
import 'package:twonly/src/views/settings/subscription/voucher_view.dart';
|
||||||
|
|
||||||
class SubscriptionView extends StatefulWidget {
|
class SubscriptionView extends StatefulWidget {
|
||||||
const SubscriptionView({super.key});
|
const SubscriptionView({super.key});
|
||||||
|
|
@ -16,8 +18,9 @@ class SubscriptionView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SubscriptionViewState extends State<SubscriptionView> {
|
class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
bool hasInternet = true;
|
bool loaded = false;
|
||||||
int ballanceInCents = 0;
|
int ballanceInCents = 0;
|
||||||
|
DateTime? nextPayment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -26,13 +29,16 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future initAsync() async {
|
Future initAsync() async {
|
||||||
// userData = await getUser();
|
|
||||||
// setState(() {});
|
|
||||||
|
|
||||||
Response_PlanBallance? ballance = await apiProvider.getPlanBallance();
|
Response_PlanBallance? ballance = await apiProvider.getPlanBallance();
|
||||||
if (ballance == null) {
|
if (ballance != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
hasInternet = false;
|
DateTime lastPaymentDateTime = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
ballance.lastPaymentDoneUnixTimestamp.toInt() * 1000);
|
||||||
|
nextPayment = lastPaymentDateTime
|
||||||
|
.add(Duration(days: ballance.paymentPeriodDays.toInt()));
|
||||||
|
ballanceInCents =
|
||||||
|
ballance.transactions.map((a) => a.depositCents.toInt()).sum;
|
||||||
|
loaded = true;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -40,17 +46,20 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Locale myLocale = Localizations.localeOf(context);
|
||||||
String formattedBalance = NumberFormat.currency(
|
String formattedBalance = NumberFormat.currency(
|
||||||
locale: 'de_DE', // Locale for Euro formatting
|
locale: myLocale.toString(),
|
||||||
symbol: '€',
|
symbol: '€',
|
||||||
decimalDigits: 2,
|
decimalDigits: 2,
|
||||||
).format(ballanceInCents / 100);
|
).format(ballanceInCents / 100);
|
||||||
|
|
||||||
|
String currentPlan = context.read<CustomChangeProvider>().plan;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.settingsSubscription),
|
title: Text(context.lang.settingsSubscription),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
|
|
@ -62,73 +71,119 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
child: Text(
|
child: Text(
|
||||||
context.watch<CustomChangeProvider>().plan,
|
currentPlan,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black,
|
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
if (currentPlan != "Family" && currentPlan != "Pro")
|
||||||
child: ListView(
|
Center(
|
||||||
children: [
|
child: Padding(
|
||||||
Center(
|
padding: const EdgeInsets.all(18.0),
|
||||||
child: Text("Upgrade your current plan."),
|
child: Text(
|
||||||
|
context.lang.upgradeToPaidPlan,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
),
|
||||||
PlanCard(
|
|
||||||
title: 'Pro',
|
|
||||||
yearlyPrice: '10€/year',
|
|
||||||
monthlyPrice: '1€/month',
|
|
||||||
features: [
|
|
||||||
'✓ Unlimited media files',
|
|
||||||
'1 additional Plus user',
|
|
||||||
'3 additional Free users',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
PlanCard(
|
|
||||||
title: 'Family',
|
|
||||||
yearlyPrice: '20€/year',
|
|
||||||
monthlyPrice: '2€/month',
|
|
||||||
features: [
|
|
||||||
'✓ All from Pro',
|
|
||||||
'4 additional Plus users',
|
|
||||||
'5 additional Free users',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Divider(),
|
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.ticket,
|
|
||||||
text: "Redeem code for additional user",
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.moneyBillTransfer,
|
|
||||||
text: "Your transaction history",
|
|
||||||
subtitle: Text("Current ballance: $formattedBalance"),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.userPlus,
|
|
||||||
text: "Manage your additional users",
|
|
||||||
subtitle: Text("Open: 3"),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.gift,
|
|
||||||
text: "Create or redeem voucher",
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
SizedBox(height: 30)
|
|
||||||
],
|
|
||||||
// tranaction
|
|
||||||
),
|
),
|
||||||
)
|
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,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (currentPlan != "Family")
|
||||||
|
PlanCard(
|
||||||
|
title: "Family",
|
||||||
|
yearlyPrice: context.lang.familyYearlyPrice,
|
||||||
|
monthlyPrice: context.lang.familyMonthlyPrice,
|
||||||
|
features: [
|
||||||
|
context.lang.familyFeature1,
|
||||||
|
context.lang.familyFeature2,
|
||||||
|
context.lang.familyFeature3,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (currentPlan == "Preview" || currentPlan == "Free") ...[
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(14.0),
|
||||||
|
child: Text(
|
||||||
|
context.lang.redeemUserInviteCode,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
if (currentPlan != "Free")
|
||||||
|
PlanCard(
|
||||||
|
title: "Free",
|
||||||
|
yearlyPrice: "",
|
||||||
|
monthlyPrice: "",
|
||||||
|
features: [
|
||||||
|
context.lang.freeFeature1,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PlanCard(
|
||||||
|
title: "Plus",
|
||||||
|
yearlyPrice: "",
|
||||||
|
monthlyPrice: "",
|
||||||
|
features: [
|
||||||
|
context.lang.plusFeature1,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SizedBox(height: 10),
|
||||||
|
if (currentPlan != "Family") Divider(),
|
||||||
|
if (currentPlan == "Family" || currentPlan == "Pro")
|
||||||
|
BetterListTile(
|
||||||
|
icon: FontAwesomeIcons.userPlus,
|
||||||
|
text: "Manage your subscription",
|
||||||
|
subtitle: (nextPayment != null)
|
||||||
|
? Text(
|
||||||
|
"Next payment: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment!)}")
|
||||||
|
: null,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
BetterListTile(
|
||||||
|
icon: FontAwesomeIcons.moneyBillTransfer,
|
||||||
|
text: context.lang.transactionHistory,
|
||||||
|
subtitle: (loaded)
|
||||||
|
? Text("${context.lang.currentBalance}: $formattedBalance")
|
||||||
|
: null,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
if (currentPlan == "Family" || currentPlan == "Pro")
|
||||||
|
BetterListTile(
|
||||||
|
icon: FontAwesomeIcons.userPlus,
|
||||||
|
text: context.lang.manageAdditionalUsers,
|
||||||
|
subtitle: (loaded) ? Text("${context.lang.open}: 3") : null,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
BetterListTile(
|
||||||
|
icon: FontAwesomeIcons.ticket,
|
||||||
|
text: context.lang.createOrRedeemVoucher,
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return VoucherView();
|
||||||
|
}));
|
||||||
|
initAsync();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 30)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -175,27 +230,28 @@ class PlanCard extends StatelessWidget {
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
if (yearlyPrice != "") SizedBox(height: 10),
|
||||||
Column(
|
if (yearlyPrice != "")
|
||||||
children: [
|
Column(
|
||||||
Text(
|
children: [
|
||||||
yearlyPrice,
|
Text(
|
||||||
textAlign: TextAlign.center,
|
yearlyPrice,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 20,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
monthlyPrice,
|
||||||
monthlyPrice,
|
textAlign: TextAlign.center,
|
||||||
textAlign: TextAlign.center,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 16,
|
||||||
fontSize: 16,
|
color: Colors.grey,
|
||||||
color: Colors.grey,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
)
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
|
|
||||||
315
lib/src/views/settings/subscription/voucher_view.dart
Normal file
315
lib/src/views/settings/subscription/voucher_view.dart
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class VoucherView extends StatefulWidget {
|
||||||
|
const VoucherView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VoucherView> createState() => _VoucherViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VoucherViewState extends State<VoucherView> {
|
||||||
|
List<Response_Voucher> vouchers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future initAsync() async {
|
||||||
|
Response_Vouchers? resVouchers = await apiProvider.getVoucherList();
|
||||||
|
if (resVouchers != null) {
|
||||||
|
setState(() {
|
||||||
|
vouchers = resVouchers.vouchers;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final openVoucher = vouchers.where((x) => !x.redeemed && !x.requested);
|
||||||
|
final redeemedVoucher = vouchers.where((x) => x.redeemed);
|
||||||
|
// final requestedVoucher = vouchers.where((x) => !x.redeemed && x.requested);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.createOrRedeemVoucher),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.redeemVoucher),
|
||||||
|
onTap: () async {
|
||||||
|
await redeemVoucher(context);
|
||||||
|
initAsync();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.createVoucher),
|
||||||
|
onTap: () async {
|
||||||
|
await showBuyVoucher(context);
|
||||||
|
initAsync();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
if (openVoucher.isNotEmpty)
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
context.lang.openVouchers,
|
||||||
|
style: TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...openVoucher.map((x) => VoucherCard(voucher: x)),
|
||||||
|
// if (requestedVoucher.isNotEmpty)
|
||||||
|
// ListTile(
|
||||||
|
// title: Text(
|
||||||
|
// context.lang.requestedVouchers,
|
||||||
|
// style: TextStyle(fontSize: 13),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ...requestedVoucher.map((x) => VoucherCard(voucher: x)),
|
||||||
|
if (redeemedVoucher.isNotEmpty)
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
context.lang.redeemedVouchers,
|
||||||
|
style: TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...redeemedVoucher.map((x) => VoucherCard(voucher: x)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VoucherCard extends StatefulWidget {
|
||||||
|
final Response_Voucher voucher;
|
||||||
|
|
||||||
|
const VoucherCard({super.key, required this.voucher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VoucherCard> createState() => _VoucherCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VoucherCardState extends State<VoucherCard> {
|
||||||
|
void _copyVoucherId() {
|
||||||
|
if (!widget.voucher.redeemed) {
|
||||||
|
Clipboard.setData(ClipboardData(text: widget.voucher.voucherId));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("${widget.voucher.voucherId} copied.")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
bool isRedeemed = widget.voucher.redeemed || widget.voucher.requested;
|
||||||
|
|
||||||
|
Locale myLocale = Localizations.localeOf(context);
|
||||||
|
String formattedValue = NumberFormat.currency(
|
||||||
|
locale: myLocale.toString(),
|
||||||
|
symbol: '€',
|
||||||
|
decimalDigits: 2,
|
||||||
|
).format(widget.voucher.valueCents.toInt() / 100);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _copyVoucherId,
|
||||||
|
child: Card(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
elevation: 5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.voucher.voucherId.toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color:
|
||||||
|
(isRedeemed) ? Colors.grey : context.color.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
formattedValue,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color:
|
||||||
|
(isRedeemed) ? Colors.grey : context.color.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future redeemVoucher(BuildContext context) async {
|
||||||
|
String voucherCode = '';
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(context.lang.redeemVoucher),
|
||||||
|
content: StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
// Convert to uppercase
|
||||||
|
setState(() {
|
||||||
|
voucherCode = value.toUpperCase();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: context.lang.enterVoucherCode,
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
// Set the text to be uppercase
|
||||||
|
textCapitalization: TextCapitalization.characters,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final res = await apiProvider.redeemVoucher(voucherCode);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (res.isSuccess) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(context.lang.voucherRedeemed)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(errorCodeToText(context, res.error))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(context.lang.ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future showBuyVoucher(BuildContext context) async {
|
||||||
|
int quantity = 1000;
|
||||||
|
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(context.lang.createVoucher),
|
||||||
|
content: StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(context.lang.createVoucherDesc),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.remove),
|
||||||
|
onPressed: () {
|
||||||
|
if (quantity > 1) {
|
||||||
|
setState(() {
|
||||||
|
if (quantity <= 100) return;
|
||||||
|
if (quantity <= 1000) {
|
||||||
|
quantity -= 100;
|
||||||
|
} else {
|
||||||
|
quantity -= 500;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
NumberFormat.currency(
|
||||||
|
locale: Localizations.localeOf(context).toString(),
|
||||||
|
symbol: '€',
|
||||||
|
decimalDigits: 2,
|
||||||
|
).format(quantity / 100),
|
||||||
|
style: TextStyle(fontSize: 24),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
if (quantity >= 1000) {
|
||||||
|
quantity += 500;
|
||||||
|
} else {
|
||||||
|
quantity += 100;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog
|
||||||
|
},
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final res = await apiProvider.buyVoucher(quantity);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (res.isSuccess) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(context.lang.voucherCreated)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(errorCodeToText(context, res.error))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(); // Close the dialog
|
||||||
|
},
|
||||||
|
child: Text(context.lang.buy),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue