add manage subscription

This commit is contained in:
otsmr 2025-05-10 16:26:25 +02:00
parent 20be849641
commit 24821c6cdc
16 changed files with 677 additions and 101 deletions

View file

@ -177,13 +177,15 @@
"month": "month",
"familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
"familyFeature2": "4 zusätzliche Plus Benutzer",
"familyFeature3": "5 zusätzliche kostenlose Benutzer",
"familyFeature3": "4 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": "Dein Guthaben",
"manageAdditionalUsers": "Zusätzlichen Benutzer verwalten",
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
"manageSubscription": "Abonnement verwalten",
"nextPayment": "Nächste Zahlung",
"open": "Offene",
"buy": "Kaufen",
"createOrRedeemVoucher": "Gutschein erstellen oder einlösen",
@ -214,6 +216,7 @@
"chargeCredit": "Guthaben aufladen",
"autoRenewal": "Automatische Verlängerung",
"autoRenewalDesc": "Du kannst dies jederzeit ändern.",
"autoRenewalLongDesc": "Wenn dein Abonnement ausläuft, wirst du automatisch auf den Vorschau-Plan zurückgestuft. Wenn du die automatische Verlängerung aktivierst, vergewissere dich bitte, dass du über genügend Guthaben für die automatische Erneuerung verfügst. Wir werden dich rechtzeitig vor der automatischen Erneuerung benachrichtigen.",
"planSuccessUpgraded": "Dein Plan wurde erfolgreich aktualisiert.",
"checkoutSubmit": "Kostenpflichtig bestellen"
}

View file

@ -335,7 +335,7 @@
"proFeature3": "3 additional Free users",
"familyFeature1": "✓ Unlimited media file uploads",
"familyFeature2": "4 additional Plus users",
"familyFeature3": "5 additional Free users",
"familyFeature3": "4 additional Free users",
"redeemUserInviteCode": "Or redeem an additional user invite code.",
"freeFeature1": "3 Media file uploads per day",
"plusFeature1": "✓ Unlimited media file uploads",
@ -343,7 +343,7 @@
"manageSubscription": "Manage your subscription",
"nextPayment": "Next payment",
"currentBalance": "Current balance",
"manageAdditionalUsers": "Manage your additional users",
"manageAdditionalUsers": "Manage additional users",
"open": "Open",
"createOrRedeemVoucher": "Buy or redeem voucher",
"createVoucher": "Buy voucher",
@ -375,6 +375,7 @@
"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.",
"planSuccessUpgraded": "Successfully upgraded your plan.",
"checkoutSubmit": "Order with a fee."
}

View file

@ -1076,7 +1076,7 @@ abstract class AppLocalizations {
/// No description provided for @familyFeature3.
///
/// In en, this message translates to:
/// **'5 additional Free users'**
/// **'4 additional Free users'**
String get familyFeature3;
/// No description provided for @redeemUserInviteCode.
@ -1124,7 +1124,7 @@ abstract class AppLocalizations {
/// No description provided for @manageAdditionalUsers.
///
/// In en, this message translates to:
/// **'Manage your additional users'**
/// **'Manage additional users'**
String get manageAdditionalUsers;
/// No description provided for @open.
@ -1307,6 +1307,12 @@ abstract class AppLocalizations {
/// **'You can change this at any time.'**
String get autoRenewalDesc;
/// No description provided for @autoRenewalLongDesc.
///
/// In en, this message translates to:
/// **'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.'**
String get autoRenewalLongDesc;
/// No description provided for @planSuccessUpgraded.
///
/// In en, this message translates to:

View file

@ -510,7 +510,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
@override
String get familyFeature3 => '5 zusätzliche kostenlose Benutzer';
String get familyFeature3 => '4 zusätzliche kostenlose Benutzer';
@override
String get redeemUserInviteCode => 'Oder löse einen zusätzlichen twonly-Code ein.';
@ -525,16 +525,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get transactionHistory => 'Transaktionshistorie';
@override
String get manageSubscription => 'Manage your subscription';
String get manageSubscription => 'Abonnement verwalten';
@override
String get nextPayment => 'Next payment';
String get nextPayment => 'Nächste Zahlung';
@override
String get currentBalance => 'Dein Guthaben';
@override
String get manageAdditionalUsers => 'Zusätzlichen Benutzer verwalten';
String get manageAdditionalUsers => 'Zusätzliche Benutzer verwalten';
@override
String get open => 'Offene';
@ -628,6 +628,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get autoRenewalDesc => 'Du kannst dies jederzeit ändern.';
@override
String get autoRenewalLongDesc => 'Wenn dein Abonnement ausläuft, wirst du automatisch auf den Vorschau-Plan zurückgestuft. Wenn du die automatische Verlängerung aktivierst, vergewissere dich bitte, dass du über genügend Guthaben für die automatische Erneuerung verfügst. Wir werden dich rechtzeitig vor der automatischen Erneuerung benachrichtigen.';
@override
String get planSuccessUpgraded => 'Dein Plan wurde erfolgreich aktualisiert.';

View file

@ -510,7 +510,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get familyFeature2 => '4 additional Plus users';
@override
String get familyFeature3 => '5 additional Free users';
String get familyFeature3 => '4 additional Free users';
@override
String get redeemUserInviteCode => 'Or redeem an additional user invite code.';
@ -534,7 +534,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get currentBalance => 'Current balance';
@override
String get manageAdditionalUsers => 'Manage your additional users';
String get manageAdditionalUsers => 'Manage additional users';
@override
String get open => 'Open';
@ -628,6 +628,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get autoRenewalDesc => 'You can change this at any time.';
@override
String get 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.';
@override
String get planSuccessUpgraded => 'Successfully upgraded your plan.';

View file

@ -29,6 +29,7 @@ class UserData {
List<String>? lastUsedEditorEmojis;
String? lastPlanBallance;
String? additionalUserInvites;
final int userId;

View file

@ -30,7 +30,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
..lastUsedEditorEmojis = (json['lastUsedEditorEmojis'] as List<dynamic>?)
?.map((e) => e as String)
.toList()
..lastPlanBallance = json['lastPlanBallance'] as String?;
..lastPlanBallance = json['lastPlanBallance'] as String?
..additionalUserInvites = json['additionalUserInvites'] as String?;
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'username': instance.username,
@ -47,6 +48,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
'lastUsedEditorEmojis': instance.lastUsedEditorEmojis,
'lastPlanBallance': instance.lastPlanBallance,
'additionalUserInvites': instance.additionalUserInvites,
'userId': instance.userId,
};

View file

@ -993,6 +993,56 @@ class ApplicationData_SwitchToPayedPlan extends $pb.GeneratedMessage {
void clearAutoRenewal() => clearField(3);
}
class ApplicationData_UpdatePlanOptions extends $pb.GeneratedMessage {
factory ApplicationData_UpdatePlanOptions({
$core.bool? autoRenewal,
}) {
final $result = create();
if (autoRenewal != null) {
$result.autoRenewal = autoRenewal;
}
return $result;
}
ApplicationData_UpdatePlanOptions._() : super();
factory ApplicationData_UpdatePlanOptions.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory ApplicationData_UpdatePlanOptions.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.UpdatePlanOptions', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
..aOB(1, _omitFieldNames ? '' : 'autoRenewal')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
ApplicationData_UpdatePlanOptions clone() => ApplicationData_UpdatePlanOptions()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
ApplicationData_UpdatePlanOptions copyWith(void Function(ApplicationData_UpdatePlanOptions) updates) => super.copyWith((message) => updates(message as ApplicationData_UpdatePlanOptions)) as ApplicationData_UpdatePlanOptions;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_UpdatePlanOptions create() => ApplicationData_UpdatePlanOptions._();
ApplicationData_UpdatePlanOptions createEmptyInstance() => create();
static $pb.PbList<ApplicationData_UpdatePlanOptions> createRepeated() => $pb.PbList<ApplicationData_UpdatePlanOptions>();
@$core.pragma('dart2js:noInline')
static ApplicationData_UpdatePlanOptions getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_UpdatePlanOptions>(create);
static ApplicationData_UpdatePlanOptions? _defaultInstance;
@$pb.TagNumber(1)
$core.bool get autoRenewal => $_getBF(0);
@$pb.TagNumber(1)
set autoRenewal($core.bool v) { $_setBool(0, v); }
@$pb.TagNumber(1)
$core.bool hasAutoRenewal() => $_has(0);
@$pb.TagNumber(1)
void clearAutoRenewal() => clearField(1);
}
class ApplicationData_CreateVoucher extends $pb.GeneratedMessage {
factory ApplicationData_CreateVoucher({
$core.int? valueCents,
@ -1253,6 +1303,56 @@ class ApplicationData_RedeemAdditionalCode extends $pb.GeneratedMessage {
void clearInviteCode() => clearField(2);
}
class ApplicationData_RemoveAdditionalUser extends $pb.GeneratedMessage {
factory ApplicationData_RemoveAdditionalUser({
$fixnum.Int64? userId,
}) {
final $result = create();
if (userId != null) {
$result.userId = userId;
}
return $result;
}
ApplicationData_RemoveAdditionalUser._() : super();
factory ApplicationData_RemoveAdditionalUser.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory ApplicationData_RemoveAdditionalUser.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.RemoveAdditionalUser', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
ApplicationData_RemoveAdditionalUser clone() => ApplicationData_RemoveAdditionalUser()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
ApplicationData_RemoveAdditionalUser copyWith(void Function(ApplicationData_RemoveAdditionalUser) updates) => super.copyWith((message) => updates(message as ApplicationData_RemoveAdditionalUser)) as ApplicationData_RemoveAdditionalUser;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_RemoveAdditionalUser create() => ApplicationData_RemoveAdditionalUser._();
ApplicationData_RemoveAdditionalUser createEmptyInstance() => create();
static $pb.PbList<ApplicationData_RemoveAdditionalUser> createRepeated() => $pb.PbList<ApplicationData_RemoveAdditionalUser>();
@$core.pragma('dart2js:noInline')
static ApplicationData_RemoveAdditionalUser getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_RemoveAdditionalUser>(create);
static ApplicationData_RemoveAdditionalUser? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 v) { $_setInt64(0, v); }
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => clearField(1);
}
class ApplicationData_GetPrekeysByUserId extends $pb.GeneratedMessage {
factory ApplicationData_GetPrekeysByUserId({
$fixnum.Int64? userId,
@ -1527,6 +1627,8 @@ enum ApplicationData_ApplicationData {
switchtopayedplan,
getaddaccountsinvites,
redeemadditionalcode,
removeadditionaluser,
updateplanoptions,
notSet
}
@ -1549,6 +1651,8 @@ class ApplicationData extends $pb.GeneratedMessage {
ApplicationData_SwitchToPayedPlan? switchtopayedplan,
ApplicationData_GetAddAccountsInvites? getaddaccountsinvites,
ApplicationData_RedeemAdditionalCode? redeemadditionalcode,
ApplicationData_RemoveAdditionalUser? removeadditionaluser,
ApplicationData_UpdatePlanOptions? updateplanoptions,
}) {
final $result = create();
if (textmessage != null) {
@ -1602,6 +1706,12 @@ class ApplicationData extends $pb.GeneratedMessage {
if (redeemadditionalcode != null) {
$result.redeemadditionalcode = redeemadditionalcode;
}
if (removeadditionaluser != null) {
$result.removeadditionaluser = removeadditionaluser;
}
if (updateplanoptions != null) {
$result.updateplanoptions = updateplanoptions;
}
return $result;
}
ApplicationData._() : super();
@ -1626,10 +1736,12 @@ class ApplicationData extends $pb.GeneratedMessage {
15 : ApplicationData_ApplicationData.switchtopayedplan,
16 : ApplicationData_ApplicationData.getaddaccountsinvites,
17 : ApplicationData_ApplicationData.redeemadditionalcode,
18 : ApplicationData_ApplicationData.removeadditionaluser,
19 : ApplicationData_ApplicationData.updateplanoptions,
0 : ApplicationData_ApplicationData.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textmessage', subBuilder: ApplicationData_TextMessage.create)
..aOM<ApplicationData_GetUserByUsername>(2, _omitFieldNames ? '' : 'getuserbyusername', subBuilder: ApplicationData_GetUserByUsername.create)
..aOM<ApplicationData_GetPrekeysByUserId>(3, _omitFieldNames ? '' : 'getprekeysbyuserid', subBuilder: ApplicationData_GetPrekeysByUserId.create)
@ -1647,6 +1759,8 @@ class ApplicationData extends $pb.GeneratedMessage {
..aOM<ApplicationData_SwitchToPayedPlan>(15, _omitFieldNames ? '' : 'Switchtopayedplan', protoName: 'Switchtopayedplan', subBuilder: ApplicationData_SwitchToPayedPlan.create)
..aOM<ApplicationData_GetAddAccountsInvites>(16, _omitFieldNames ? '' : 'getaddaccountsinvites', subBuilder: ApplicationData_GetAddAccountsInvites.create)
..aOM<ApplicationData_RedeemAdditionalCode>(17, _omitFieldNames ? '' : 'redeemadditionalcode', subBuilder: ApplicationData_RedeemAdditionalCode.create)
..aOM<ApplicationData_RemoveAdditionalUser>(18, _omitFieldNames ? '' : 'removeadditionaluser', subBuilder: ApplicationData_RemoveAdditionalUser.create)
..aOM<ApplicationData_UpdatePlanOptions>(19, _omitFieldNames ? '' : 'updateplanoptions', subBuilder: ApplicationData_UpdatePlanOptions.create)
..hasRequiredFields = false
;
@ -1860,6 +1974,28 @@ class ApplicationData extends $pb.GeneratedMessage {
void clearRedeemadditionalcode() => clearField(17);
@$pb.TagNumber(17)
ApplicationData_RedeemAdditionalCode ensureRedeemadditionalcode() => $_ensure(16);
@$pb.TagNumber(18)
ApplicationData_RemoveAdditionalUser get removeadditionaluser => $_getN(17);
@$pb.TagNumber(18)
set removeadditionaluser(ApplicationData_RemoveAdditionalUser v) { setField(18, v); }
@$pb.TagNumber(18)
$core.bool hasRemoveadditionaluser() => $_has(17);
@$pb.TagNumber(18)
void clearRemoveadditionaluser() => clearField(18);
@$pb.TagNumber(18)
ApplicationData_RemoveAdditionalUser ensureRemoveadditionaluser() => $_ensure(17);
@$pb.TagNumber(19)
ApplicationData_UpdatePlanOptions get updateplanoptions => $_getN(18);
@$pb.TagNumber(19)
set updateplanoptions(ApplicationData_UpdatePlanOptions v) { setField(19, v); }
@$pb.TagNumber(19)
$core.bool hasUpdateplanoptions() => $_has(18);
@$pb.TagNumber(19)
void clearUpdateplanoptions() => clearField(19);
@$pb.TagNumber(19)
ApplicationData_UpdatePlanOptions ensureUpdateplanoptions() => $_ensure(18);
}
class Response_PreKey extends $pb.GeneratedMessage {

View file

@ -153,8 +153,10 @@ const ApplicationData$json = {
{'1': 'Switchtopayedplan', '3': 15, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.SwitchToPayedPlan', '9': 0, '10': 'Switchtopayedplan'},
{'1': 'getaddaccountsinvites', '3': 16, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetAddAccountsInvites', '9': 0, '10': 'getaddaccountsinvites'},
{'1': 'redeemadditionalcode', '3': 17, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.RedeemAdditionalCode', '9': 0, '10': 'redeemadditionalcode'},
{'1': 'removeadditionaluser', '3': 18, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.RemoveAdditionalUser', '9': 0, '10': 'removeadditionaluser'},
{'1': 'updateplanoptions', '3': 19, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.UpdatePlanOptions', '9': 0, '10': 'updateplanoptions'},
],
'3': [ApplicationData_TextMessage$json, ApplicationData_GetUserByUsername$json, ApplicationData_UpdateGoogleFcmToken$json, ApplicationData_GetUserById$json, ApplicationData_RedeemVoucher$json, ApplicationData_SwitchToPayedPlan$json, ApplicationData_CreateVoucher$json, ApplicationData_GetLocation$json, ApplicationData_GetVouchers$json, ApplicationData_GetAvailablePlans$json, ApplicationData_GetAddAccountsInvites$json, ApplicationData_GetCurrentPlanInfos$json, ApplicationData_RedeemAdditionalCode$json, ApplicationData_GetPrekeysByUserId$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json, ApplicationData_DownloadData$json],
'3': [ApplicationData_TextMessage$json, ApplicationData_GetUserByUsername$json, ApplicationData_UpdateGoogleFcmToken$json, ApplicationData_GetUserById$json, ApplicationData_RedeemVoucher$json, ApplicationData_SwitchToPayedPlan$json, ApplicationData_UpdatePlanOptions$json, ApplicationData_CreateVoucher$json, ApplicationData_GetLocation$json, ApplicationData_GetVouchers$json, ApplicationData_GetAvailablePlans$json, ApplicationData_GetAddAccountsInvites$json, ApplicationData_GetCurrentPlanInfos$json, ApplicationData_RedeemAdditionalCode$json, ApplicationData_RemoveAdditionalUser$json, ApplicationData_GetPrekeysByUserId$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json, ApplicationData_DownloadData$json],
'8': [
{'1': 'ApplicationData'},
],
@ -215,6 +217,14 @@ const ApplicationData_SwitchToPayedPlan$json = {
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_UpdatePlanOptions$json = {
'1': 'UpdatePlanOptions',
'2': [
{'1': 'auto_renewal', '3': 1, '4': 1, '5': 8, '10': 'autoRenewal'},
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_CreateVoucher$json = {
'1': 'CreateVoucher',
@ -256,6 +266,14 @@ const ApplicationData_RedeemAdditionalCode$json = {
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_RemoveAdditionalUser$json = {
'1': 'RemoveAdditionalUser',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_GetPrekeysByUserId$json = {
'1': 'GetPrekeysByUserId',
@ -326,24 +344,30 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'Y2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0QWRkQWNjb3VudHNJbnZpdGVzSA'
'BSFWdldGFkZGFjY291bnRzaW52aXRlcxJsChRyZWRlZW1hZGRpdGlvbmFsY29kZRgRIAEoCzI2'
'LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlJlZGVlbUFkZGl0aW9uYWxDb2RlSA'
'BSFHJlZGVlbWFkZGl0aW9uYWxjb2RlGmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANS'
'BnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRG'
'F0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEg'
'ASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgAS'
'gJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGikK'
'DVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpwChFTd2l0Y2hUb1BheW'
'VkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5X21vbnRobHkYAiABKAhSCnBh'
'eU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUmVuZXdhbBowCg1DcmVhdGVWb3'
'VjaGVyEh8KC3ZhbHVlX2NlbnRzGAEgASgNUgp2YWx1ZUNlbnRzGg0KC0dldExvY2F0aW9uGg0K'
'C0dldFZvdWNoZXJzGhMKEUdldEF2YWlsYWJsZVBsYW5zGhcKFUdldEFkZEFjY291bnRzSW52aX'
'RlcxoVChNHZXRDdXJyZW50UGxhbkluZm9zGjcKFFJlZGVlbUFkZGl0aW9uYWxDb2RlEh8KC2lu'
'dml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2'
'VyX2lkGAEgASgDUgZ1c2VySWQaOwoOR2V0VXBsb2FkVG9rZW4SKQoQcmVjaXBpZW50c19jb3Vu'
'dBgBIAEoDVIPcmVjaXBpZW50c0NvdW50GokBCgpVcGxvYWREYXRhEiEKDHVwbG9hZF90b2tlbh'
'gBIAEoDFILdXBsb2FkVG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXQSEgoEZGF0YRgDIAEo'
'DFIEZGF0YRIfCghjaGVja3N1bRgEIAEoDEgAUghjaGVja3N1bYgBAUILCglfY2hlY2tzdW0aTQ'
'oMRG93bmxvYWREYXRhEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuEhYK'
'Bm9mZnNldBgCIAEoDVIGb2Zmc2V0QhEKD0FwcGxpY2F0aW9uRGF0YQ==');
'BSFHJlZGVlbWFkZGl0aW9uYWxjb2RlEmwKFHJlbW92ZWFkZGl0aW9uYWx1c2VyGBIgASgLMjYu'
'Y2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVtb3ZlQWRkaXRpb25hbFVzZXJIAF'
'IUcmVtb3ZlYWRkaXRpb25hbHVzZXISYwoRdXBkYXRlcGxhbm9wdGlvbnMYEyABKAsyMy5jbGll'
'bnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGRhdGVQbGFuT3B0aW9uc0gAUhF1cGRhdG'
'VwbGFub3B0aW9ucxpqCgtUZXh0TWVzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoE'
'Ym9keRgDIAEoDFIEYm9keRIgCglwdXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3'
'B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5h'
'bWUaNQoUVXBkYXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2xlRm'
'NtGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBopCg1SZWRlZW1Wb3Vj'
'aGVyEhgKB3ZvdWNoZXIYASABKAlSB3ZvdWNoZXIacAoRU3dpdGNoVG9QYXllZFBsYW4SFwoHcG'
'xhbl9pZBgBIAEoCVIGcGxhbklkEh8KC3BheV9tb250aGx5GAIgASgIUgpwYXlNb250aGx5EiEK'
'DGF1dG9fcmVuZXdhbBgDIAEoCFILYXV0b1JlbmV3YWwaNgoRVXBkYXRlUGxhbk9wdGlvbnMSIQ'
'oMYXV0b19yZW5ld2FsGAEgASgIUgthdXRvUmVuZXdhbBowCg1DcmVhdGVWb3VjaGVyEh8KC3Zh'
'bHVlX2NlbnRzGAEgASgNUgp2YWx1ZUNlbnRzGg0KC0dldExvY2F0aW9uGg0KC0dldFZvdWNoZX'
'JzGhMKEUdldEF2YWlsYWJsZVBsYW5zGhcKFUdldEFkZEFjY291bnRzSW52aXRlcxoVChNHZXRD'
'dXJyZW50UGxhbkluZm9zGjcKFFJlZGVlbUFkZGl0aW9uYWxDb2RlEh8KC2ludml0ZV9jb2RlGA'
'IgASgJUgppbnZpdGVDb2RlGi8KFFJlbW92ZUFkZGl0aW9uYWxVc2VyEhcKB3VzZXJfaWQYASAB'
'KANSBnVzZXJJZBotChJHZXRQcmVrZXlzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
'lkGjsKDkdldFVwbG9hZFRva2VuEikKEHJlY2lwaWVudHNfY291bnQYASABKA1SD3JlY2lwaWVu'
'dHNDb3VudBqJAQoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9rZW4YASABKAxSC3VwbG9hZFRva2'
'VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAyABKAxSBGRhdGESHwoIY2hlY2tz'
'dW0YBCABKAxIAFIIY2hlY2tzdW2IAQFCCwoJX2NoZWNrc3VtGk0KDERvd25sb2FkRGF0YRIlCg'
'5kb3dubG9hZF90b2tlbhgBIAEoDFINZG93bmxvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9m'
'ZnNldEIRCg9BcHBsaWNhdGlvbkRhdGE=');
@$core.Deprecated('Use responseDescriptor instead')
const Response$json = {

View file

@ -1068,6 +1068,7 @@ class Response_PlanBallance extends $pb.GeneratedMessage {
$fixnum.Int64? lastPaymentDoneUnixTimestamp,
$core.Iterable<Response_Transaction>? transactions,
$core.Iterable<Response_AdditionalAccount>? additionalAccounts,
$core.bool? autoRenewal,
}) {
final $result = create();
if (usedDailyMediaUploadLimit != null) {
@ -1088,6 +1089,9 @@ class Response_PlanBallance extends $pb.GeneratedMessage {
if (additionalAccounts != null) {
$result.additionalAccounts.addAll(additionalAccounts);
}
if (autoRenewal != null) {
$result.autoRenewal = autoRenewal;
}
return $result;
}
Response_PlanBallance._() : super();
@ -1101,6 +1105,7 @@ class Response_PlanBallance extends $pb.GeneratedMessage {
..aInt64(4, _omitFieldNames ? '' : 'lastPaymentDoneUnixTimestamp')
..pc<Response_Transaction>(5, _omitFieldNames ? '' : 'transactions', $pb.PbFieldType.PM, subBuilder: Response_Transaction.create)
..pc<Response_AdditionalAccount>(6, _omitFieldNames ? '' : 'additionalAccounts', $pb.PbFieldType.PM, subBuilder: Response_AdditionalAccount.create)
..aOB(7, _omitFieldNames ? '' : 'autoRenewal')
..hasRequiredFields = false
;
@ -1166,6 +1171,15 @@ class Response_PlanBallance extends $pb.GeneratedMessage {
@$pb.TagNumber(6)
$core.List<Response_AdditionalAccount> get additionalAccounts => $_getList(5);
@$pb.TagNumber(7)
$core.bool get autoRenewal => $_getBF(6);
@$pb.TagNumber(7)
set autoRenewal($core.bool v) { $_setBool(6, v); }
@$pb.TagNumber(7)
$core.bool hasAutoRenewal() => $_has(6);
@$pb.TagNumber(7)
void clearAutoRenewal() => clearField(7);
}
class Response_Location extends $pb.GeneratedMessage {

View file

@ -198,10 +198,12 @@ const Response_PlanBallance$json = {
{'1': 'last_payment_done_unix_timestamp', '3': 4, '4': 1, '5': 3, '9': 1, '10': 'lastPaymentDoneUnixTimestamp', '17': true},
{'1': 'transactions', '3': 5, '4': 3, '5': 11, '6': '.server_to_client.Response.Transaction', '10': 'transactions'},
{'1': 'additional_accounts', '3': 6, '4': 3, '5': 11, '6': '.server_to_client.Response.AdditionalAccount', '10': 'additionalAccounts'},
{'1': 'auto_renewal', '3': 7, '4': 1, '5': 8, '9': 2, '10': 'autoRenewal', '17': true},
],
'8': [
{'1': '_payment_period_days'},
{'1': '_last_payment_done_unix_timestamp'},
{'1': '_auto_renewal'},
],
};
@ -318,7 +320,7 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
'1lZBgDIAEoCFIIcmVkZWVtZWQSHAoJcmVxdWVzdGVkGAQgASgIUglyZXF1ZXN0ZWQSOQoZY3Jl'
'YXRlZF9hdF91bml4X3RpbWVzdGFtcBgFIAEoA1IWY3JlYXRlZEF0VW5peFRpbWVzdGFtcBpKCg'
'hWb3VjaGVycxI+Cgh2b3VjaGVycxgBIAMoCzIiLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2Uu'
'Vm91Y2hlclIIdm91Y2hlcnMa+gMKDFBsYW5CYWxsYW5jZRJACh11c2VkX2RhaWx5X21lZGlhX3'
'Vm91Y2hlclIIdm91Y2hlcnMaswQKDFBsYW5CYWxsYW5jZRJACh11c2VkX2RhaWx5X21lZGlhX3'
'VwbG9hZF9saW1pdBgBIAEoA1IZdXNlZERhaWx5TWVkaWFVcGxvYWRMaW1pdBI+Chx1c2VkX3Vw'
'bG9hZF9tZWRpYV9zaXplX2xpbWl0GAIgASgDUhh1c2VkVXBsb2FkTWVkaWFTaXplTGltaXQSMw'
'oTcGF5bWVudF9wZXJpb2RfZGF5cxgDIAEoA0gAUhFwYXltZW50UGVyaW9kRGF5c4gBARJLCiBs'
@ -326,34 +328,35 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
'VVbml4VGltZXN0YW1wiAEBEkoKDHRyYW5zYWN0aW9ucxgFIAMoCzImLnNlcnZlcl90b19jbGll'
'bnQuUmVzcG9uc2UuVHJhbnNhY3Rpb25SDHRyYW5zYWN0aW9ucxJdChNhZGRpdGlvbmFsX2FjY2'
'91bnRzGAYgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRpdGlvbmFsQWNjb3Vu'
'dFISYWRkaXRpb25hbEFjY291bnRzQhYKFF9wYXltZW50X3BlcmlvZF9kYXlzQiMKIV9sYXN0X3'
'BheW1lbnRfZG9uZV91bml4X3RpbWVzdGFtcBpOCghMb2NhdGlvbhIWCgZjb3VudHkYASABKAlS'
'BmNvdW50eRIWCgZyZWdpb24YAiABKAlSBnJlZ2lvbhISCgRjaXR5GAMgASgJUgRjaXR5GjAKBl'
'ByZUtleRIOCgJpZBgBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkatAMKCFVzZXJE'
'YXRhEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBI7CgdwcmVrZXlzGAIgAygLMiEuc2VydmVyX3'
'RvX2NsaWVudC5SZXNwb25zZS5QcmVLZXlSB3ByZWtleXMSHwoIdXNlcm5hbWUYByABKAxIAFII'
'dXNlcm5hbWWIAQESMwoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDEgBUhFwdWJsaWNJZGVudG'
'l0eUtleYgBARIoCg1zaWduZWRfcHJla2V5GAQgASgMSAJSDHNpZ25lZFByZWtleYgBARI7Chdz'
'aWduZWRfcHJla2V5X3NpZ25hdHVyZRgFIAEoDEgDUhVzaWduZWRQcmVrZXlTaWduYXR1cmWIAQ'
'ESLQoQc2lnbmVkX3ByZWtleV9pZBgGIAEoA0gEUg5zaWduZWRQcmVrZXlJZIgBAUILCglfdXNl'
'cm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3NpZ25lZF9wcmVrZXlCGgoYX3NpZ2'
'5lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2V5X2lkGlkKC1VwbG9hZFRva2Vu'
'EiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2FkVG9rZW4SJwoPZG93bmxvYWRfdG9rZW5zGA'
'IgAygMUg5kb3dubG9hZFRva2VucxrTBQoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVz'
'ZXJpZBgCIAEoA0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbG'
'xlbmdlEkoKC3VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5V'
'cGxvYWRUb2tlbkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b1'
'9jbGllbnQuUmVzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgM'
'SABSCWF1dGh0b2tlbhJBCghsb2NhdGlvbhgHIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVzcG'
'9uc2UuTG9jYXRpb25IAFIIbG9jYXRpb24SUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNlcnZl'
'cl90b19jbGllbnQuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEjgKBX'
'BsYW5zGAkgASgLMiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFucxJN'
'CgxwbGFuYmFsbGFuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5CYW'
'xsYW5jZUgAUgxwbGFuYmFsbGFuY2USQQoIdm91Y2hlcnMYCyABKAsyIy5zZXJ2ZXJfdG9fY2xp'
'ZW50LlJlc3BvbnNlLlZvdWNoZXJzSABSCHZvdWNoZXJzEl8KEmFkZGFjY291bnRzaW52aXRlcx'
'gMIAEoCzItLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuQWRkQWNjb3VudHNJbnZpdGVzSABS'
'EmFkZGFjY291bnRzaW52aXRlc0IECgJPayKFAQoQVHJhbnNhY3Rpb25UeXBlcxIKCgZSZWZ1bm'
'QQABITCg9Wb3VjaGVyUmVkZWVtZWQQARISCg5Wb3VjaGVyQ3JlYXRlZBACEggKBENhc2gQAxIP'
'CgtQbGFuVXBncmFkZRAEEgsKB1Vua25vd24QBRIUChBUaGFua3NGb3JUZXN0aW5nEAZCCgoIUm'
'VzcG9uc2U=');
'dFISYWRkaXRpb25hbEFjY291bnRzEiYKDGF1dG9fcmVuZXdhbBgHIAEoCEgCUgthdXRvUmVuZX'
'dhbIgBAUIWChRfcGF5bWVudF9wZXJpb2RfZGF5c0IjCiFfbGFzdF9wYXltZW50X2RvbmVfdW5p'
'eF90aW1lc3RhbXBCDwoNX2F1dG9fcmVuZXdhbBpOCghMb2NhdGlvbhIWCgZjb3VudHkYASABKA'
'lSBmNvdW50eRIWCgZyZWdpb24YAiABKAlSBnJlZ2lvbhISCgRjaXR5GAMgASgJUgRjaXR5GjAK'
'BlByZUtleRIOCgJpZBgBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkatAMKCFVzZX'
'JEYXRhEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBI7CgdwcmVrZXlzGAIgAygLMiEuc2VydmVy'
'X3RvX2NsaWVudC5SZXNwb25zZS5QcmVLZXlSB3ByZWtleXMSHwoIdXNlcm5hbWUYByABKAxIAF'
'IIdXNlcm5hbWWIAQESMwoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDEgBUhFwdWJsaWNJZGVu'
'dGl0eUtleYgBARIoCg1zaWduZWRfcHJla2V5GAQgASgMSAJSDHNpZ25lZFByZWtleYgBARI7Ch'
'dzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgFIAEoDEgDUhVzaWduZWRQcmVrZXlTaWduYXR1cmWI'
'AQESLQoQc2lnbmVkX3ByZWtleV9pZBgGIAEoA0gEUg5zaWduZWRQcmVrZXlJZIgBAUILCglfdX'
'Nlcm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3NpZ25lZF9wcmVrZXlCGgoYX3Np'
'Z25lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2V5X2lkGlkKC1VwbG9hZFRva2'
'VuEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2FkVG9rZW4SJwoPZG93bmxvYWRfdG9rZW5z'
'GAIgAygMUg5kb3dubG9hZFRva2VucxrTBQoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBn'
'VzZXJpZBgCIAEoA0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hh'
'bGxlbmdlEkoKC3VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS'
'5VcGxvYWRUb2tlbkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90'
'b19jbGllbnQuUmVzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgAS'
'gMSABSCWF1dGh0b2tlbhJBCghsb2NhdGlvbhgHIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVz'
'cG9uc2UuTG9jYXRpb25IAFIIbG9jYXRpb24SUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNlcn'
'Zlcl90b19jbGllbnQuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEjgK'
'BXBsYW5zGAkgASgLMiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFucx'
'JNCgxwbGFuYmFsbGFuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5C'
'YWxsYW5jZUgAUgxwbGFuYmFsbGFuY2USQQoIdm91Y2hlcnMYCyABKAsyIy5zZXJ2ZXJfdG9fY2'
'xpZW50LlJlc3BvbnNlLlZvdWNoZXJzSABSCHZvdWNoZXJzEl8KEmFkZGFjY291bnRzaW52aXRl'
'cxgMIAEoCzItLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuQWRkQWNjb3VudHNJbnZpdGVzSA'
'BSEmFkZGFjY291bnRzaW52aXRlc0IECgJPayKFAQoQVHJhbnNhY3Rpb25UeXBlcxIKCgZSZWZ1'
'bmQQABITCg9Wb3VjaGVyUmVkZWVtZWQQARISCg5Wb3VjaGVyQ3JlYXRlZBACEggKBENhc2gQAx'
'IPCgtQbGFuVXBncmFkZRAEEgsKB1Vua25vd24QBRIUChBUaGFua3NGb3JUZXN0aW5nEAZCCgoI'
'UmVzcG9uc2U=');

View file

@ -433,6 +433,34 @@ class ApiProvider {
return null;
}
Future<List<Response_AddAccountsInvite>?> getAdditionalUserInvites() async {
var get = ApplicationData_GetAddAccountsInvites();
var appData = ApplicationData()..getaddaccountsinvites = get;
var req = createClientToServerFromApplicationData(appData);
Result res = await sendRequestSync(req);
if (res.isSuccess) {
server.Response_Ok ok = res.value;
if (ok.hasAddaccountsinvites()) {
return ok.addaccountsinvites.invites;
}
}
return null;
}
Future<Result> updatePlanOptions(bool autoRenewal) async {
var get = ApplicationData_UpdatePlanOptions()..autoRenewal = autoRenewal;
var appData = ApplicationData()..updateplanoptions = get;
var req = createClientToServerFromApplicationData(appData);
return await sendRequestSync(req);
}
Future<Result> removeAdditionalUser(Int64 userId) async {
var get = ApplicationData_RemoveAdditionalUser()..userId = userId;
var appData = ApplicationData()..removeadditionaluser = get;
var req = createClientToServerFromApplicationData(appData);
return await sendRequestSync(req);
}
Future<Result> buyVoucher(int valueInCents) async {
var get = ApplicationData_CreateVoucher()..valueCents = valueInCents;
var appData = ApplicationData()..createvoucher = get;

View file

@ -1,15 +1,248 @@
import 'package:flutter/widgets.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api_utils.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/settings/subscription/subscription_view.dart';
Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
List<Response_AddAccountsInvite>? ballance;
final user = await getUser();
if (user == null) return ballance;
ballance = await apiProvider.getAdditionalUserInvites();
if (ballance != null) {
user.additionalUserInvites =
jsonEncode(ballance.map((x) => x.writeToJson()).toList());
await updateUser(user);
} else if (user.lastPlanBallance != null) {
try {
List<String> decoded = jsonDecode(user.additionalUserInvites!);
ballance =
decoded.map((x) => Response_AddAccountsInvite.fromJson(x)).toList();
} catch (e) {
Logger("additional_users_view.dart").shout("from json: $e");
}
}
return ballance;
}
class AdditionalUsersView extends StatefulWidget {
const AdditionalUsersView({super.key});
const AdditionalUsersView({super.key, required this.ballance});
final Response_PlanBallance? ballance;
@override
State<AdditionalUsersView> createState() => _AdditionalUsersViewState();
}
class _AdditionalUsersViewState extends State<AdditionalUsersView> {
List<Response_AddAccountsInvite>? additionalInvites;
Response_PlanBallance? ballance;
@override
void initState() {
super.initState();
ballance = widget.ballance;
initAsync(false);
}
Future initAsync(bool force) async {
additionalInvites = await loadAdditionalUserInvites();
if (force) {
ballance = await loadPlanBallance();
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return const Placeholder();
List<Response_AddAccountsInvite> plusInvites = [];
List<Response_AddAccountsInvite> freeInvites = [];
if (additionalInvites != null) {
plusInvites =
additionalInvites!.where((x) => x.planId == "Plus").toList();
freeInvites =
additionalInvites!.where((x) => x.planId == "Free").toList();
}
return Scaffold(
appBar: AppBar(
title: Text(context.lang.manageAdditionalUsers),
),
body: ListView(
children: [
if (ballance != null && ballance!.additionalAccounts.isNotEmpty)
ListTile(
title: Text(
"Your additional users",
style: TextStyle(fontSize: 13),
),
),
if (ballance != null)
...ballance!.additionalAccounts.map((e) => AdditionalAccount(
account: e,
refresh: () {
initAsync(true);
},
)),
if (plusInvites.isNotEmpty)
ListTile(
title: Text(
"Additional invite codes for \"Plus\" users",
style: TextStyle(fontSize: 13),
),
),
GridView.count(
crossAxisCount: 2,
physics: NeverScrollableScrollPhysics(),
childAspectRatio: 16 / 5,
shrinkWrap: true,
children: plusInvites.map((x) => AdditionalUserInvite(x)).toList(),
),
if (freeInvites.isNotEmpty)
ListTile(
title: Text(
"Additional invite codes for \"Free\" users",
style: TextStyle(fontSize: 13),
),
),
GridView.count(
crossAxisCount: 2,
physics: NeverScrollableScrollPhysics(),
childAspectRatio: 16 / 5,
shrinkWrap: true,
children: freeInvites.map((x) => AdditionalUserInvite(x)).toList(),
),
],
),
);
}
}
class AdditionalAccount extends StatefulWidget {
const AdditionalAccount(
{super.key, required this.account, required this.refresh});
final Function() refresh;
final Response_AdditionalAccount account;
@override
State<AdditionalAccount> createState() => _AdditionalAccountState();
}
class _AdditionalAccountState extends State<AdditionalAccount> {
late String username;
@override
void initState() {
super.initState();
username = widget.account.userId.toString();
initAsync();
}
Future initAsync() async {
final contact = await twonlyDatabase.contactsDao
.getContactByUserId(widget.account.userId.toInt())
.getSingleOrNull();
if (contact != null) {
username = getContactDisplayName(contact);
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
username,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text(
widget.account.planId,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
IconButton(
icon: FaIcon(FontAwesomeIcons.userXmark, size: 16),
onPressed: () async {
bool remove = await showAlertDialog(
context,
"Remove this additional user",
"The additional user will automatically be downgraded to the preview plan after removal and you will receive a new invitation code to give to another person.");
if (remove) {
Result res = await apiProvider
.removeAdditionalUser(widget.account.userId);
if (!context.mounted) return;
if (res.isSuccess) {
widget.refresh();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorCodeToText(context, res.error))),
);
}
}
},
),
],
),
),
);
}
}
class AdditionalUserInvite extends StatefulWidget {
final Response_AddAccountsInvite invite;
const AdditionalUserInvite(this.invite, {super.key});
@override
State<AdditionalUserInvite> createState() => _AdditionalUserInviteState();
}
class _AdditionalUserInviteState extends State<AdditionalUserInvite> {
void _copyVoucherId() {
Clipboard.setData(ClipboardData(text: widget.invite.inviteCode));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${widget.invite.inviteCode} copied.")),
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _copyVoucherId,
child: Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
widget.invite.inviteCode.toUpperCase(),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
),
);
}
}

View file

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
class ManageSubscriptionView extends StatefulWidget {
const ManageSubscriptionView(
{super.key, required this.ballance, required this.nextPayment});
final Response_PlanBallance? ballance;
final DateTime? nextPayment;
@override
State<ManageSubscriptionView> createState() => _ManageSubscriptionViewState();
}
class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
Response_PlanBallance? ballance;
bool? autoRenewal;
@override
void initState() {
super.initState();
ballance = widget.ballance;
if (ballance != null) {
autoRenewal = ballance!.autoRenewal;
}
initAsync(false);
}
Future initAsync(bool force) async {
if (force) {
ballance = await loadPlanBallance();
if (ballance != null) {
autoRenewal = ballance!.autoRenewal;
}
}
setState(() {});
}
Future toggleRenewalOption() async {
Result res = await apiProvider.updatePlanOptions(!autoRenewal!);
if (res.isError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errorCodeToText(context, res.error))),
);
}
await initAsync(true);
}
@override
Widget build(BuildContext context) {
String planId = context.read<CustomChangeProvider>().plan;
Locale myLocale = Localizations.localeOf(context);
bool? paidMonthly = ballance?.paymentPeriodDays == MONTHLY_PAYMENT_DAYS;
return Scaffold(
appBar: AppBar(
title: Text(context.lang.manageSubscription),
),
body: ListView(
children: [
PlanCard(planId: planId, paidMonthly: paidMonthly),
SizedBox(height: 20),
if (widget.nextPayment != null)
ListTile(
title: Text(
"${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(widget.nextPayment!)}",
),
),
if (autoRenewal != null)
ListTile(
title: Text(context.lang.autoRenewal),
subtitle: Text(
context.lang.autoRenewalLongDesc,
style: TextStyle(fontSize: 12),
),
onTap: toggleRenewalOption,
trailing: Checkbox(
value: autoRenewal!,
onChanged: (a) {
toggleRenewalOption();
},
),
),
ListTile(
title: Text("Kündigen"),
onTap: () async {},
),
],
),
);
}
}

View file

@ -10,7 +10,9 @@ import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/views/settings/subscription/additional_users_view.dart';
import 'package:twonly/src/views/settings/subscription/checkout_view.dart';
import 'package:twonly/src/views/settings/subscription/manage_subscription_view.dart';
import 'package:twonly/src/views/settings/subscription/transaction_view.dart';
import 'package:twonly/src/views/settings/subscription/voucher_view.dart';
@ -47,7 +49,6 @@ Future<Response_PlanBallance?> loadPlanBallance() async {
Logger("subscription_view.dart").shout("from json: $e");
}
}
return ballance;
}
@ -227,7 +228,14 @@ class _SubscriptionViewState extends State<SubscriptionView> {
? Text(
"${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}")
: null,
onTap: () {},
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return ManageSubscriptionView(
ballance: ballance, nextPayment: nextPayment);
}));
initAsync();
},
),
BetterListTile(
icon: FontAwesomeIcons.moneyBillTransfer,
@ -250,7 +258,15 @@ class _SubscriptionViewState extends State<SubscriptionView> {
icon: FontAwesomeIcons.userPlus,
text: context.lang.manageAdditionalUsers,
subtitle: (loaded) ? Text("${context.lang.open}: 3") : null,
onTap: () {},
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return AdditionalUsersView(
ballance: ballance,
);
}));
initAsync();
},
),
BetterListTile(
icon: FontAwesomeIcons.ticket,
@ -284,13 +300,14 @@ class PlanCard extends StatelessWidget {
final String planId;
final Function()? onTap;
final int? refund;
final bool? paidMonthly;
const PlanCard({
super.key,
const PlanCard(
{super.key,
required this.planId,
this.refund,
this.onTap,
});
this.paidMonthly});
@override
Widget build(BuildContext context) {
@ -349,9 +366,10 @@ class PlanCard extends StatelessWidget {
),
),
if (yearlyPrice != 0) SizedBox(height: 10),
if (yearlyPrice != 0)
if (yearlyPrice != 0 && paidMonthly == null)
Column(
children: [
if (paidMonthly == null || paidMonthly!)
Text(
"${localePrizing(context, yearlyPrice)}/${context.lang.year}",
textAlign: TextAlign.center,
@ -360,6 +378,7 @@ class PlanCard extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
if (paidMonthly == null || !paidMonthly!)
Text(
"${localePrizing(context, monthlyPrice)}/${context.lang.month}",
textAlign: TextAlign.center,
@ -369,7 +388,18 @@ class PlanCard extends StatelessWidget {
),
),
],
)
),
if (paidMonthly != null)
Text(
(paidMonthly!)
? "${localePrizing(context, monthlyPrice)}/${context.lang.month}"
: "${localePrizing(context, yearlyPrice)}/${context.lang.year}",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 10),

View file

@ -35,7 +35,6 @@ class _VoucherViewState extends State<VoucherView> {
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),
@ -65,14 +64,6 @@ class _VoucherViewState extends State<VoucherView> {
),
),
...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(