From afb1806cb10822a6aef84bc19966cf941db7eac6 Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 18 Jul 2025 14:22:34 +0200 Subject: [PATCH] fix #241 and #250 --- CHANGELOG.md | 11 ++ lib/app.dart | 5 + lib/globals.dart | 1 + lib/src/database/twonly_database.g.dart | 36 +++--- lib/src/localization/app_de.arb | 13 +- lib/src/localization/app_en.arb | 14 +-- .../generated/app_localizations.dart | 38 +++--- .../generated/app_localizations_de.dart | 24 ++-- .../generated/app_localizations_en.dart | 24 ++-- lib/src/model/json/userdata.dart | 5 +- lib/src/model/json/userdata.g.dart | 7 +- lib/src/services/api.service.dart | 1 + lib/src/views/chats/chat_list.view.dart | 40 +++--- .../manage_subscription.view.dart | 8 +- .../subscription/subscription.view.dart | 118 +++++------------- 15 files changed, 143 insertions(+), 202 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1b73c4c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## 0.0.58 (WIP) + +- twonly now has a free plan and is now financed by donations and an optional subscription with more features (coming soon) +- Implementing iOS gestures to close images +- Improved the chat messages view, including better citation view and display times. +- Updated onboarding screens and simplified the registration view +- The sender is displayed in the top right corner when a media file is opened. +- Images are now stored as WebP to save storage +- Multiple bug fixes \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 5773553..0de5d7d 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -33,6 +33,10 @@ class _AppState extends State with WidgetsBindingObserver { setUserPlan(); }; + globalCallbackUpdatePlan = (String planId) { + context.read().updatePlan(planId); + }; + initAsync(); } @@ -86,6 +90,7 @@ class _AppState extends State with WidgetsBindingObserver { void dispose() { WidgetsBinding.instance.removeObserver(this); globalCallbackConnectionState = ({required bool isConnected}) {}; + globalCallbackUpdatePlan = (String planId) {}; super.dispose(); } diff --git a/lib/globals.dart b/lib/globals.dart index 0ac4af8..297872e 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -19,6 +19,7 @@ void Function({required bool isConnected}) globalCallbackConnectionState = ({ required bool isConnected, }) {}; void Function() globalCallbackAppIsOutdated = () {}; +void Function(String planId) globalCallbackUpdatePlan = (String planId) {}; bool globalIsAppInBackground = true; int globalBestFriendUserId = -1; diff --git a/lib/src/database/twonly_database.g.dart b/lib/src/database/twonly_database.g.dart index bbeb9c9..d29e873 100644 --- a/lib/src/database/twonly_database.g.dart +++ b/lib/src/database/twonly_database.g.dart @@ -46,7 +46,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { 'my_avatar_counter', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: false, - defaultValue: Constant(0)); + defaultValue: const Constant(0)); static const VerificationMeta _acceptedMeta = const VerificationMeta('accepted'); @override @@ -56,7 +56,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("accepted" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _requestedMeta = const VerificationMeta('requested'); @override @@ -66,7 +66,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("requested" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _blockedMeta = const VerificationMeta('blocked'); @override @@ -76,7 +76,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("blocked" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _verifiedMeta = const VerificationMeta('verified'); @override @@ -86,7 +86,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("verified" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _archivedMeta = const VerificationMeta('archived'); @override @@ -96,7 +96,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); @override late final GeneratedColumn pinned = GeneratedColumn( @@ -105,7 +105,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _deletedMeta = const VerificationMeta('deleted'); @override @@ -115,7 +115,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _alsoBestFriendMeta = const VerificationMeta('alsoBestFriend'); @override @@ -125,7 +125,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("also_best_friend" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _deleteMessagesAfterXMinutesMeta = const VerificationMeta('deleteMessagesAfterXMinutes'); @override @@ -134,7 +134,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { 'delete_messages_after_x_minutes', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: false, - defaultValue: Constant(60 * 24)); + defaultValue: const Constant(60 * 24)); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override @@ -150,7 +150,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { 'total_media_counter', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: false, - defaultValue: Constant(0)); + defaultValue: const Constant(0)); static const VerificationMeta _lastMessageSendMeta = const VerificationMeta('lastMessageSend'); @override @@ -190,7 +190,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { 'flame_counter', aliasedName, false, type: DriftSqlType.int, requiredDuringInsert: false, - defaultValue: Constant(0)); + defaultValue: const Constant(0)); @override List get $columns => [ userId, @@ -1160,7 +1160,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("acknowledge_by_user" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _mediaStoredMeta = const VerificationMeta('mediaStored'); @override @@ -1170,7 +1170,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("media_stored" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); @override late final GeneratedColumnWithTypeConverter downloadState = GeneratedColumn('download_state', aliasedName, false, @@ -1187,7 +1187,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("acknowledge_by_server" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); static const VerificationMeta _errorWhileSendingMeta = const VerificationMeta('errorWhileSending'); @override @@ -1197,7 +1197,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("error_while_sending" IN (0, 1))'), - defaultValue: Constant(false)); + defaultValue: const Constant(false)); @override late final GeneratedColumnWithTypeConverter mediaRetransmissionState = GeneratedColumn( @@ -2099,7 +2099,7 @@ class $MediaUploadsTable extends MediaUploads static JsonTypeConverter2 $converterstate = const EnumNameConverter(UploadState.values); static JsonTypeConverter2> - $convertermetadata = MediaUploadMetadataConverter(); + $convertermetadata = const MediaUploadMetadataConverter(); static JsonTypeConverter2?> $convertermetadatan = JsonTypeConverter2.asNullable($convertermetadata); @@ -2108,7 +2108,7 @@ class $MediaUploadsTable extends MediaUploads static TypeConverter?, String?> $convertermessageIdsn = NullAwareTypeConverter.wrap($convertermessageIds); static JsonTypeConverter2> - $converterencryptionData = MediaEncryptionDataConverter(); + $converterencryptionData = const MediaEncryptionDataConverter(); static JsonTypeConverter2?> $converterencryptionDatan = JsonTypeConverter2.asNullable($converterencryptionData); diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 0c8c787..24e05e4 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -213,15 +213,16 @@ "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", + "proFeature3": "Zusatzfunktionen (coming-soon)", + "proFeature4": "Cloud-Backup verschlüsselt (coming-soon)", "year": "year", "month": "month", - "familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", + "familyFeature1": "✓ Alles von Pro", "familyFeature2": "4 zusätzliche Plus Benutzer", - "familyFeature3": "4 zusätzliche kostenlose Benutzer", "redeemUserInviteCode": "Oder löse einen twonly-Code ein.", - "freeFeature1": "3 Medien-Datei-Uploads pro Tag", + "freeFeature1": "10 Medien-Datei-Uploads pro Tag", "plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", + "plusFeature2": "Zusatzfunktionen (coming-soon)", "transactionHistory": "Transaktionshistorie", "currentBalance": "Dein Guthaben", "manageAdditionalUsers": "Zusätzliche Benutzer verwalten", @@ -329,7 +330,5 @@ "appOutdatedBtn": "Jetzt aktualisieren.", "doubleClickToReopen": "Doppelklicken zum\nerneuten Öffnen.", "retransmissionRequested": "Wird erneut versucht.", - "testPaymentMethod": "twonly befindet sich derzeit in einer Testphase und kann nur mit einem Einladungscode vollständig genutzt werden. Es gibt derzeit keine Zahlungsmethode, um Ihr twonly-Guthaben aufzuladen!", - "testingAccountTitle": "Tester-Zugang", - "testingAccountBody": "Danke für dein Interesse! Wir werden deine Anfrage prüfen und den Plan so schnell wie möglich aktivieren. Da wir uns jedoch noch in einer Testphase befinden, ist die Anzahl der Tester-Konten begrenzt. Wir werden dich jedoch benachrichtigen, sobald dir ein Platz zugewiesen wurde." + "testPaymentMethod": "Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!" } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 39dbc72..fb3e4b5 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -367,15 +367,16 @@ "month": "month", "proFeature1": "✓ Unlimited media file uploads", "proFeature2": "1 additional Plus user", - "proFeature3": "3 additional Free users", - "familyFeature1": "✓ Unlimited media file uploads", + "proFeature3": "Cloud-Backup encrypted (coming-soon)", + "proFeature4": "Additional features (coming-soon)", + "familyFeature1": "✓ All from Pro", "familyFeature2": "4 additional Plus users", - "familyFeature3": "4 additional Free users", "redeemUserInviteCode": "Or redeem a twonly-Code.", "redeemUserInviteCodeTitle": "Redeem twonly-Code", "redeemUserInviteCodeSuccess": "Your plan has been successfully adjusted.", - "freeFeature1": "3 Media file uploads per day", + "freeFeature1": "10 Media file uploads per day", "plusFeature1": "✓ Unlimited media file uploads", + "plusFeature2": "Additional features (coming-soon)", "transactionHistory": "Your transaction history", "manageSubscription": "Manage your subscription", "nextPayment": "Next payment", @@ -410,7 +411,6 @@ "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.", "autoRenewalLongDesc": "When your subscription expires, you will automatically be downgraded to the Preview plan. If you activate the automatic renewal, please make sure that you have enough credit for the automatic renewal. We will notify you in good time before the automatic renewal.", @@ -486,7 +486,5 @@ "appOutdatedBtn": "Update Now", "doubleClickToReopen": "Double-click\nto open again", "retransmissionRequested": "Retransmission requested", - "testPaymentMethod": "twonly is currently in a test phase and can only be used in full with an invitation code. There is currently no payment method to top up your twonly credit!", - "testingAccountTitle": "Tester account activation", - "testingAccountBody": "Thank you for your interest! We will check your request and activate the plan as soon as possible. However, as we are currently still in a test phase, the number of tester accounts is limited. However, we will notify you as soon as you have been allocated a place." + "testPaymentMethod": "Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!" } \ 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 82a1c1d..33ff2f4 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1307,13 +1307,19 @@ abstract class AppLocalizations { /// No description provided for @proFeature3. /// /// In en, this message translates to: - /// **'3 additional Free users'** + /// **'Cloud-Backup encrypted (coming-soon)'** String get proFeature3; + /// No description provided for @proFeature4. + /// + /// In en, this message translates to: + /// **'Additional features (coming-soon)'** + String get proFeature4; + /// No description provided for @familyFeature1. /// /// In en, this message translates to: - /// **'✓ Unlimited media file uploads'** + /// **'✓ All from Pro'** String get familyFeature1; /// No description provided for @familyFeature2. @@ -1322,12 +1328,6 @@ abstract class AppLocalizations { /// **'4 additional Plus users'** String get familyFeature2; - /// No description provided for @familyFeature3. - /// - /// In en, this message translates to: - /// **'4 additional Free users'** - String get familyFeature3; - /// No description provided for @redeemUserInviteCode. /// /// In en, this message translates to: @@ -1349,7 +1349,7 @@ abstract class AppLocalizations { /// No description provided for @freeFeature1. /// /// In en, this message translates to: - /// **'3 Media file uploads per day'** + /// **'10 Media file uploads per day'** String get freeFeature1; /// No description provided for @plusFeature1. @@ -1358,6 +1358,12 @@ abstract class AppLocalizations { /// **'✓ Unlimited media file uploads'** String get plusFeature1; + /// No description provided for @plusFeature2. + /// + /// In en, this message translates to: + /// **'Additional features (coming-soon)'** + String get plusFeature2; + /// No description provided for @transactionHistory. /// /// In en, this message translates to: @@ -2015,20 +2021,8 @@ abstract class AppLocalizations { /// No description provided for @testPaymentMethod. /// /// In en, this message translates to: - /// **'twonly is currently in a test phase and can only be used in full with an invitation code. There is currently no payment method to top up your twonly credit!'** + /// **'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'** String get testPaymentMethod; - - /// No description provided for @testingAccountTitle. - /// - /// In en, this message translates to: - /// **'Tester account activation'** - String get testingAccountTitle; - - /// No description provided for @testingAccountBody. - /// - /// In en, this message translates to: - /// **'Thank you for your interest! We will check your request and activate the plan as soon as possible. However, as we are currently still in a test phase, the number of tester accounts is limited. However, we will notify you as soon as you have been allocated a place.'** - String get testingAccountBody; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 6e0e7ca..c5d67e0 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -684,17 +684,17 @@ class AppLocalizationsDe extends AppLocalizations { String get proFeature2 => '1 zusätzlicher Plus Benutzer'; @override - String get proFeature3 => '3 zusätzliche kostenlose Benutzer'; + String get proFeature3 => 'Zusatzfunktionen (coming-soon)'; @override - String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; + String get proFeature4 => 'Cloud-Backup verschlüsselt (coming-soon)'; + + @override + String get familyFeature1 => '✓ Alles von Pro'; @override String get familyFeature2 => '4 zusätzliche Plus Benutzer'; - @override - String get familyFeature3 => '4 zusätzliche kostenlose Benutzer'; - @override String get redeemUserInviteCode => 'Oder löse einen twonly-Code ein.'; @@ -706,11 +706,14 @@ class AppLocalizationsDe extends AppLocalizations { 'Dein Plan wurde erfolgreich angepasst.'; @override - String get freeFeature1 => '3 Medien-Datei-Uploads pro Tag'; + String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag'; @override String get plusFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads'; + @override + String get plusFeature2 => 'Zusatzfunktionen (coming-soon)'; + @override String get transactionHistory => 'Transaktionshistorie'; @@ -1069,12 +1072,5 @@ class AppLocalizationsDe extends AppLocalizations { @override String get testPaymentMethod => - 'twonly befindet sich derzeit in einer Testphase und kann nur mit einem Einladungscode vollständig genutzt werden. Es gibt derzeit keine Zahlungsmethode, um Ihr twonly-Guthaben aufzuladen!'; - - @override - String get testingAccountTitle => 'Tester-Zugang'; - - @override - String get testingAccountBody => - 'Danke für dein Interesse! Wir werden deine Anfrage prüfen und den Plan so schnell wie möglich aktivieren. Da wir uns jedoch noch in einer Testphase befinden, ist die Anzahl der Tester-Konten begrenzt. Wir werden dich jedoch benachrichtigen, sobald dir ein Platz zugewiesen wurde.'; + 'Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index b7f41e0..6d1044c 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -679,17 +679,17 @@ class AppLocalizationsEn extends AppLocalizations { String get proFeature2 => '1 additional Plus user'; @override - String get proFeature3 => '3 additional Free users'; + String get proFeature3 => 'Cloud-Backup encrypted (coming-soon)'; @override - String get familyFeature1 => '✓ Unlimited media file uploads'; + String get proFeature4 => 'Additional features (coming-soon)'; + + @override + String get familyFeature1 => '✓ All from Pro'; @override String get familyFeature2 => '4 additional Plus users'; - @override - String get familyFeature3 => '4 additional Free users'; - @override String get redeemUserInviteCode => 'Or redeem a twonly-Code.'; @@ -701,11 +701,14 @@ class AppLocalizationsEn extends AppLocalizations { 'Your plan has been successfully adjusted.'; @override - String get freeFeature1 => '3 Media file uploads per day'; + String get freeFeature1 => '10 Media file uploads per day'; @override String get plusFeature1 => '✓ Unlimited media file uploads'; + @override + String get plusFeature2 => 'Additional features (coming-soon)'; + @override String get transactionHistory => 'Your transaction history'; @@ -1063,12 +1066,5 @@ class AppLocalizationsEn extends AppLocalizations { @override String get testPaymentMethod => - 'twonly is currently in a test phase and can only be used in full with an invitation code. There is currently no payment method to top up your twonly credit!'; - - @override - String get testingAccountTitle => 'Tester account activation'; - - @override - String get testingAccountBody => - 'Thank you for your interest! We will check your request and activate the plan as soon as possible. However, as we are currently still in a test phase, the number of tester accounts is limited. However, we will notify you as soon as you have been allocated a place.'; + 'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'; } diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart index f77f5e0..ebecc93 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.dart @@ -67,9 +67,6 @@ class UserData { DateTime? signalLastSignedPreKeyUpdated; - @JsonKey(defaultValue: false) - bool requestedTesterAccount = false; - // -- Custom DATA -- @JsonKey(defaultValue: 100_000) @@ -78,6 +75,8 @@ class UserData { @JsonKey(defaultValue: 100_000) int currentSignedPreKeyIndexStart = 100_000; + List? lastChangeLogHash; + // --- BACKUP --- DateTime? nextTimeToShowBackupNotice; diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart index 817f4ef..040f438 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.g.dart @@ -49,12 +49,13 @@ UserData _$UserDataFromJson(Map json) => UserData( json['signalLastSignedPreKeyUpdated'] == null ? null : DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String) - ..requestedTesterAccount = - json['requestedTesterAccount'] as bool? ?? false ..currentPreKeyIndexStart = (json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000 ..currentSignedPreKeyIndexStart = (json['currentSignedPreKeyIndexStart'] as num?)?.toInt() ?? 100000 + ..lastChangeLogHash = (json['lastChangeLogHash'] as List?) + ?.map((e) => (e as num).toInt()) + .toList() ..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null ? null : DateTime.parse(json['nextTimeToShowBackupNotice'] as String) @@ -91,9 +92,9 @@ Map _$UserDataToJson(UserData instance) => { 'myBestFriendContactId': instance.myBestFriendContactId, 'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated?.toIso8601String(), - 'requestedTesterAccount': instance.requestedTesterAccount, 'currentPreKeyIndexStart': instance.currentPreKeyIndexStart, 'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart, + 'lastChangeLogHash': instance.lastChangeLogHash, 'nextTimeToShowBackupNotice': instance.nextTimeToShowBackupNotice?.toIso8601String(), 'backupServer': instance.backupServer, diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 798b4c0..6bb4c2d 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -366,6 +366,7 @@ class ApiService { user.subscriptionPlan = authenticated.plan; return user; }); + globalCallbackUpdatePlan(authenticated.plan); } Log.info('websocket is authenticated'); unawaited(onAuthenticated()); diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index 2780bc5..a93bd09 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -88,32 +88,34 @@ class _ChatListViewState extends State { @override Widget build(BuildContext context) { final isConnected = context.watch().isConnected; + final planId = context.watch().plan; return Scaffold( appBar: AppBar( title: Row(children: [ const Text('twonly '), - GestureDetector( - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) { - return const SubscriptionView(); - })); - }, - child: Container( - decoration: BoxDecoration( - color: context.color.primary, - borderRadius: BorderRadius.circular(15), - ), - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), - child: Text( - context.watch().plan, - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: isDarkMode(context) ? Colors.black : Colors.white, + if (planId != 'Free') + GestureDetector( + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return const SubscriptionView(); + })); + }, + child: Container( + decoration: BoxDecoration( + color: context.color.primary, + borderRadius: BorderRadius.circular(15), + ), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), + child: Text( + planId, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: isDarkMode(context) ? Colors.black : Colors.white, + ), ), ), ), - ), ]), actions: [ if (showFeedbackShortcut) diff --git a/lib/src/views/settings/subscription/manage_subscription.view.dart b/lib/src/views/settings/subscription/manage_subscription.view.dart index 943fb40..5e79bbe 100644 --- a/lib/src/views/settings/subscription/manage_subscription.view.dart +++ b/lib/src/views/settings/subscription/manage_subscription.view.dart @@ -64,7 +64,7 @@ class _ManageSubscriptionViewState extends State { final planId = context.read().plan; final myLocale = Localizations.localeOf(context); final paidMonthly = ballance?.paymentPeriodDays == MONTHLY_PAYMENT_DAYS; - final isAdditionalUser = planId == 'Free' || planId == 'Plus'; + final isPayingUser = planId == 'Family' || planId == 'Pro'; return Scaffold( appBar: AppBar( title: Text(context.lang.manageSubscription), @@ -72,14 +72,14 @@ class _ManageSubscriptionViewState extends State { body: ListView( children: [ PlanCard(planId: planId, paidMonthly: paidMonthly), - if (!isAdditionalUser) const SizedBox(height: 20), - if (widget.nextPayment != null && !isAdditionalUser) + if (isPayingUser) const SizedBox(height: 20), + if (widget.nextPayment != null && isPayingUser) ListTile( title: Text( '${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(widget.nextPayment!)}', ), ), - if (autoRenewal != null && !isAdditionalUser) + if (autoRenewal != null && isPayingUser) ListTile( title: Text(context.lang.autoRenewal), subtitle: Text( diff --git a/lib/src/views/settings/subscription/subscription.view.dart b/lib/src/views/settings/subscription/subscription.view.dart index fa50b67..0411797 100644 --- a/lib/src/views/settings/subscription/subscription.view.dart +++ b/lib/src/views/settings/subscription/subscription.view.dart @@ -2,7 +2,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; @@ -13,7 +12,6 @@ import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; import 'package:twonly/src/views/settings/subscription/additional_users.view.dart'; import 'package:twonly/src/views/settings/subscription/checkout.view.dart'; @@ -129,59 +127,21 @@ class _SubscriptionViewState extends State { additionalOwnerName = ownerId.toString(); } } - final user = await getUser(); - if (user != null) { - testerRequested = user.requestedTesterAccount; - // if (kDebugMode) { - // testerRequested = false; - // } - } setState(() {}); } - Future _submitTesterRequest() async { - final user = await getUser(); - if (user == null) return; - final response = await http.post( - Uri.parse('https://twonly.theconnectapp.de/subscribe.twonly.php'), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: { - 'feedback': '[TESTER REQUEST] ${user.username} ${user.userId}', - }, - ); - if (!mounted) return; - if (response.statusCode == 200) { - // Handle successful response - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Your request has been submitted.')), - ); - await updateUserdata((u) { - u.requestedTesterAccount = true; - return u; - }); - await initAsync(); - } else { - // Handle error response - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Failed to submit your request.')), - ); - } - } - @override Widget build(BuildContext context) { final myLocale = Localizations.localeOf(context); String? formattedBalance; DateTime? nextPayment; final currentPlan = context.read().plan; - final isAdditionalUser = currentPlan == 'Free' || currentPlan == 'Plus'; + final isPayingUser = currentPlan == 'Family' || currentPlan == 'Pro'; if (ballance != null) { final lastPaymentDateTime = DateTime.fromMillisecondsSinceEpoch( ballance!.lastPaymentDoneUnixTimestamp.toInt() * 1000); - if (!isAdditionalUser) { + if (isPayingUser) { nextPayment = lastPaymentDateTime .add(Duration(days: ballance!.paymentPeriodDays.toInt())); } @@ -262,20 +222,6 @@ class _SubscriptionViewState extends State { ), ), ), - if (currentPlan != 'Tester' && !testerRequested) - PlanCard( - planId: 'Tester', - onTap: () async { - final activate = await showAlertDialog( - context, - context.lang.testingAccountTitle, - context.lang.testingAccountBody, - ); - if (activate) { - await _submitTesterRequest(); - } - }, - ), if (currentPlan != 'Family' && currentPlan != 'Pro') PlanCard( planId: 'Pro', @@ -307,7 +253,7 @@ class _SubscriptionViewState extends State { await initAsync(); }, ), - if (currentPlan == 'Preview' || currentPlan == 'Free') ...[ + if (!isPayingUser) ...[ const SizedBox(height: 10), Center( child: Padding( @@ -320,14 +266,6 @@ class _SubscriptionViewState extends State { ), ), const SizedBox(height: 10), - if (currentPlan != 'Free') - PlanCard( - planId: 'Free', - onTap: () async { - await redeemUserInviteCode(context, 'Free'); - await initAsync(); - }, - ), PlanCard( planId: 'Plus', onTap: () async { @@ -338,25 +276,24 @@ class _SubscriptionViewState extends State { ], const SizedBox(height: 10), if (currentPlan != 'Family') const Divider(), - if (currentPlan != 'Preview') - BetterListTile( - icon: FontAwesomeIcons.gears, - text: context.lang.manageSubscription, - subtitle: (nextPayment != null) - ? Text( - '${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}') - : null, - onTap: () async { - await Navigator.push(context, - MaterialPageRoute(builder: (context) { - return ManageSubscriptionView( - ballance: ballance, - nextPayment: nextPayment, - ); - })); - await initAsync(); - }, - ), + BetterListTile( + icon: FontAwesomeIcons.gears, + text: context.lang.manageSubscription, + subtitle: (nextPayment != null) + ? Text( + '${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}') + : null, + onTap: () async { + await Navigator.push(context, + MaterialPageRoute(builder: (context) { + return ManageSubscriptionView( + ballance: ballance, + nextPayment: nextPayment, + ); + })); + await initAsync(); + }, + ), BetterListTile( icon: FontAwesomeIcons.moneyBillTransfer, text: context.lang.transactionHistory, @@ -373,7 +310,7 @@ class _SubscriptionViewState extends State { })); }, ), - if (!isAdditionalUser) + if (isPayingUser) BetterListTile( icon: FontAwesomeIcons.userPlus, text: context.lang.manageAdditionalUsers, @@ -434,24 +371,25 @@ class PlanCard extends StatelessWidget { final yearlyPrice = getPlanPrice(planId, paidMonthly: false); final monthlyPrice = getPlanPrice(planId, paidMonthly: true); var features = []; + final isPayingUser = planId == 'Family' || planId == 'Pro'; switch (planId) { case 'Free': features = [context.lang.freeFeature1]; case 'Plus': - features = [context.lang.plusFeature1]; + features = [context.lang.plusFeature1, context.lang.plusFeature2]; case 'Tester': case 'Pro': features = [ context.lang.proFeature1, context.lang.proFeature2, - context.lang.proFeature3 + context.lang.proFeature3, + context.lang.proFeature4, ]; case 'Family': features = [ context.lang.familyFeature1, context.lang.familyFeature2, - context.lang.familyFeature3 ]; default: } @@ -481,7 +419,7 @@ class PlanCard extends StatelessWidget { ), ), if (yearlyPrice != 0) const SizedBox(height: 10), - if (yearlyPrice != 0 && paidMonthly == null) + if (isPayingUser) Column( children: [ if (paidMonthly == null || paidMonthly!) @@ -504,7 +442,7 @@ class PlanCard extends StatelessWidget { ), ], ), - if (paidMonthly != null) + if (isPayingUser && paidMonthly != null) Text( (paidMonthly!) ? '${localePrizing(context, monthlyPrice)}/${context.lang.month}'