delete user account

This commit is contained in:
otsmr 2025-05-31 23:58:06 +02:00
parent a9f94e46a3
commit 86d87b84ac
16 changed files with 357 additions and 72 deletions

View file

@ -128,6 +128,9 @@
"settingsHelpTerms": "Nutzungsbedingungen",
"settingsAppearanceTheme": "Theme",
"settingsAccountDeleteAccount": "Konto löschen",
"settingsAccountDeleteAccountWithBallance": "Im nächsten Schritt kannst du auswählen, was du mit dem Restguthaben ({credit}) machen willst.",
"settingsAccountDeleteAccountNoInternet": "Zum Löschen deines Accounts ist eine Internetverbindung erforderlich.",
"settingsAccountDeleteAccountNoBallance": "Wenn du dein Konto gelöscht hast, gibt es keinen Weg zurück.",
"settingsAccountDeleteModalTitle": "Bist du sicher?",
"settingsAccountDeleteModalBody": "Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.",
"contactVerifyNumberTitle": "Sicherheitsnummer verifizieren",

View file

@ -224,6 +224,9 @@
"settingsAppearanceTheme": "Theme",
"@settingsAppearanceTheme": {},
"settingsAccountDeleteAccount": "Delete account",
"settingsAccountDeleteAccountWithBallance": "In the next step, you can select what you want to to with the remaining credit ({credit}).",
"settingsAccountDeleteAccountNoBallance": "Once you delete your account, there is no going back.",
"settingsAccountDeleteAccountNoInternet": "An Internet connection is required to delete your account.",
"@settingsAccountDeleteAccount": {},
"settingsAccountDeleteModalTitle": "Are you sure?",
"@settingsAccountDeleteModalTitle": {},

View file

@ -770,6 +770,24 @@ abstract class AppLocalizations {
/// **'Delete account'**
String get settingsAccountDeleteAccount;
/// No description provided for @settingsAccountDeleteAccountWithBallance.
///
/// In en, this message translates to:
/// **'In the next step, you can select what you want to to with the remaining credit ({credit}).'**
String settingsAccountDeleteAccountWithBallance(Object credit);
/// No description provided for @settingsAccountDeleteAccountNoBallance.
///
/// In en, this message translates to:
/// **'Once you delete your account, there is no going back.'**
String get settingsAccountDeleteAccountNoBallance;
/// No description provided for @settingsAccountDeleteAccountNoInternet.
///
/// In en, this message translates to:
/// **'An Internet connection is required to delete your account.'**
String get settingsAccountDeleteAccountNoInternet;
/// No description provided for @settingsAccountDeleteModalTitle.
///
/// In en, this message translates to:

View file

@ -375,6 +375,19 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settingsAccountDeleteAccount => 'Konto löschen';
@override
String settingsAccountDeleteAccountWithBallance(Object credit) {
return 'Im nächsten Schritt kannst du auswählen, was du mit dem Restguthaben ($credit) machen willst.';
}
@override
String get settingsAccountDeleteAccountNoBallance =>
'Wenn du dein Konto gelöscht hast, gibt es keinen Weg zurück.';
@override
String get settingsAccountDeleteAccountNoInternet =>
'Zum Löschen deines Accounts ist eine Internetverbindung erforderlich.';
@override
String get settingsAccountDeleteModalTitle => 'Bist du sicher?';

View file

@ -370,6 +370,19 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingsAccountDeleteAccount => 'Delete account';
@override
String settingsAccountDeleteAccountWithBallance(Object credit) {
return 'In the next step, you can select what you want to to with the remaining credit ($credit).';
}
@override
String get settingsAccountDeleteAccountNoBallance =>
'Once you delete your account, there is no going back.';
@override
String get settingsAccountDeleteAccountNoInternet =>
'An Internet connection is required to delete your account.';
@override
String get settingsAccountDeleteModalTitle => 'Are you sure?';

View file

@ -1851,6 +1851,38 @@ class ApplicationData_DownloadDone extends $pb.GeneratedMessage {
void clearDownloadToken() => clearField(1);
}
class ApplicationData_DeleteAccount extends $pb.GeneratedMessage {
factory ApplicationData_DeleteAccount() => create();
ApplicationData_DeleteAccount._() : super();
factory ApplicationData_DeleteAccount.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory ApplicationData_DeleteAccount.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.DeleteAccount', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
..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_DeleteAccount clone() => ApplicationData_DeleteAccount()..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_DeleteAccount copyWith(void Function(ApplicationData_DeleteAccount) updates) => super.copyWith((message) => updates(message as ApplicationData_DeleteAccount)) as ApplicationData_DeleteAccount;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_DeleteAccount create() => ApplicationData_DeleteAccount._();
ApplicationData_DeleteAccount createEmptyInstance() => create();
static $pb.PbList<ApplicationData_DeleteAccount> createRepeated() => $pb.PbList<ApplicationData_DeleteAccount>();
@$core.pragma('dart2js:noInline')
static ApplicationData_DeleteAccount getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_DeleteAccount>(create);
static ApplicationData_DeleteAccount? _defaultInstance;
}
enum ApplicationData_ApplicationData {
textmessage,
getuserbyusername,
@ -1875,6 +1907,7 @@ enum ApplicationData_ApplicationData {
uploaddone,
getsignedprekeybyuserid,
updatesignedprekey,
deleteaccount,
notSet
}
@ -1903,6 +1936,7 @@ class ApplicationData extends $pb.GeneratedMessage {
ApplicationData_UploadDone? uploaddone,
ApplicationData_GetSignedPreKeyByUserId? getsignedprekeybyuserid,
ApplicationData_UpdateSignedPreKey? updatesignedprekey,
ApplicationData_DeleteAccount? deleteaccount,
}) {
final $result = create();
if (textmessage != null) {
@ -1974,6 +2008,9 @@ class ApplicationData extends $pb.GeneratedMessage {
if (updatesignedprekey != null) {
$result.updatesignedprekey = updatesignedprekey;
}
if (deleteaccount != null) {
$result.deleteaccount = deleteaccount;
}
return $result;
}
ApplicationData._() : super();
@ -2004,10 +2041,11 @@ class ApplicationData extends $pb.GeneratedMessage {
21 : ApplicationData_ApplicationData.uploaddone,
22 : ApplicationData_ApplicationData.getsignedprekeybyuserid,
23 : ApplicationData_ApplicationData.updatesignedprekey,
24 : ApplicationData_ApplicationData.deleteaccount,
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, 18, 19, 20, 21, 22, 23])
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24])
..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)
@ -2031,6 +2069,7 @@ class ApplicationData extends $pb.GeneratedMessage {
..aOM<ApplicationData_UploadDone>(21, _omitFieldNames ? '' : 'uploaddone', subBuilder: ApplicationData_UploadDone.create)
..aOM<ApplicationData_GetSignedPreKeyByUserId>(22, _omitFieldNames ? '' : 'getsignedprekeybyuserid', subBuilder: ApplicationData_GetSignedPreKeyByUserId.create)
..aOM<ApplicationData_UpdateSignedPreKey>(23, _omitFieldNames ? '' : 'updatesignedprekey', subBuilder: ApplicationData_UpdateSignedPreKey.create)
..aOM<ApplicationData_DeleteAccount>(24, _omitFieldNames ? '' : 'deleteaccount', subBuilder: ApplicationData_DeleteAccount.create)
..hasRequiredFields = false
;
@ -2310,6 +2349,17 @@ class ApplicationData extends $pb.GeneratedMessage {
void clearUpdatesignedprekey() => clearField(23);
@$pb.TagNumber(23)
ApplicationData_UpdateSignedPreKey ensureUpdatesignedprekey() => $_ensure(22);
@$pb.TagNumber(24)
ApplicationData_DeleteAccount get deleteaccount => $_getN(23);
@$pb.TagNumber(24)
set deleteaccount(ApplicationData_DeleteAccount v) { setField(24, v); }
@$pb.TagNumber(24)
$core.bool hasDeleteaccount() => $_has(23);
@$pb.TagNumber(24)
void clearDeleteaccount() => clearField(24);
@$pb.TagNumber(24)
ApplicationData_DeleteAccount ensureDeleteaccount() => $_ensure(23);
}
class Response_PreKey extends $pb.GeneratedMessage {

View file

@ -159,8 +159,9 @@ const ApplicationData$json = {
{'1': 'uploaddone', '3': 21, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.UploadDone', '9': 0, '10': 'uploaddone'},
{'1': 'getsignedprekeybyuserid', '3': 22, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetSignedPreKeyByUserId', '9': 0, '10': 'getsignedprekeybyuserid'},
{'1': 'updatesignedprekey', '3': 23, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.UpdateSignedPreKey', '9': 0, '10': 'updatesignedprekey'},
{'1': 'deleteaccount', '3': 24, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.DeleteAccount', '9': 0, '10': 'deleteaccount'},
],
'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_GetSignedPreKeyByUserId$json, ApplicationData_UpdateSignedPreKey$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json, ApplicationData_UploadDone$json, ApplicationData_DownloadData$json, ApplicationData_DownloadDone$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_GetSignedPreKeyByUserId$json, ApplicationData_UpdateSignedPreKey$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json, ApplicationData_UploadDone$json, ApplicationData_DownloadData$json, ApplicationData_DownloadDone$json, ApplicationData_DeleteAccount$json],
'8': [
{'1': 'ApplicationData'},
],
@ -352,6 +353,11 @@ const ApplicationData_DownloadDone$json = {
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_DeleteAccount$json = {
'1': 'DeleteAccount',
};
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dG1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
@ -394,32 +400,34 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRTaWduZWRQcmVLZXlCeVVzZXJJZEgAUhdnZXRz'
'aWduZWRwcmVrZXlieXVzZXJpZBJmChJ1cGRhdGVzaWduZWRwcmVrZXkYFyABKAsyNC5jbGllbn'
'RfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGRhdGVTaWduZWRQcmVLZXlIAFISdXBkYXRl'
'c2lnbmVkcHJla2V5GmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCg'
'Rib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCgpf'
'cHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2Vybm'
'FtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVG'
'Y20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGikKDVJlZGVlbVZvdW'
'NoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2hlchpwChFTd2l0Y2hUb1BheWVkUGxhbhIXCgdw'
'bGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5X21vbnRobHkYAiABKAhSCnBheU1vbnRobHkSIQ'
'oMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUmVuZXdhbBo2ChFVcGRhdGVQbGFuT3B0aW9ucxIh'
'CgxhdXRvX3JlbmV3YWwYASABKAhSC2F1dG9SZW5ld2FsGjAKDUNyZWF0ZVZvdWNoZXISHwoLdm'
'FsdWVfY2VudHMYASABKA1SCnZhbHVlQ2VudHMaDQoLR2V0TG9jYXRpb24aDQoLR2V0Vm91Y2hl'
'cnMaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUKE0dldE'
'N1cnJlbnRQbGFuSW5mb3MaNwoUUmVkZWVtQWRkaXRpb25hbENvZGUSHwoLaW52aXRlX2NvZGUY'
'AiABKAlSCmludml0ZUNvZGUaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIA'
'EoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2Vy'
'SWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGp'
'sBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVk'
'UHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF'
'9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaOwoOR2V0VXBs'
'b2FkVG9rZW4SKQoQcmVjaXBpZW50c19jb3VudBgBIAEoDVIPcmVjaXBpZW50c0NvdW50GokBCg'
'pVcGxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2FkVG9rZW4SFgoGb2Zmc2V0'
'GAIgASgNUgZvZmZzZXQSEgoEZGF0YRgDIAEoDFIEZGF0YRIfCghjaGVja3N1bRgEIAEoDEgAUg'
'hjaGVja3N1bYgBAUILCglfY2hlY2tzdW0aWgoKVXBsb2FkRG9uZRIhCgx1cGxvYWRfdG9rZW4Y'
'ASABKAxSC3VwbG9hZFRva2VuEikKEHJlY2lwaWVudHNfY291bnQYAiABKA1SD3JlY2lwaWVudH'
'NDb3VudBpNCgxEb3dubG9hZERhdGESJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd25sb2Fk'
'VG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXQaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2'
'FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuQhEKD0FwcGxpY2F0aW9uRGF0YQ==');
'c2lnbmVkcHJla2V5ElcKDWRlbGV0ZWFjY291bnQYGCABKAsyLy5jbGllbnRfdG9fc2VydmVyLk'
'FwcGxpY2F0aW9uRGF0YS5EZWxldGVBY2NvdW50SABSDWRlbGV0ZWFjY291bnQaagoLVGV4dE1l'
'c3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZHkSIAoJcH'
'VzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0VXNlckJ5'
'VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbV'
'Rva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1'
'c2VyX2lkGAEgASgDUgZ1c2VySWQaKQoNUmVkZWVtVm91Y2hlchIYCgd2b3VjaGVyGAEgASgJUg'
'd2b3VjaGVyGnAKEVN3aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW5faWQYASABKAlSBnBsYW5JZBIf'
'CgtwYXlfbW9udGhseRgCIAEoCFIKcGF5TW9udGhseRIhCgxhdXRvX3JlbmV3YWwYAyABKAhSC2'
'F1dG9SZW5ld2FsGjYKEVVwZGF0ZVBsYW5PcHRpb25zEiEKDGF1dG9fcmVuZXdhbBgBIAEoCFIL'
'YXV0b1JlbmV3YWwaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdW'
'VDZW50cxoNCgtHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFu'
'cxoXChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZW'
'RlZW1BZGRpdGlvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRovChRS'
'ZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2'
'V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlC'
'eVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleR'
'IoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJl'
'a2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKA'
'xSFXNpZ25lZFByZWtleVNpZ25hdHVyZRo7Cg5HZXRVcGxvYWRUb2tlbhIpChByZWNpcGllbnRz'
'X2NvdW50GAEgASgNUg9yZWNpcGllbnRzQ291bnQaiQEKClVwbG9hZERhdGESIQoMdXBsb2FkX3'
'Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRh'
'GAMgASgMUgRkYXRhEh8KCGNoZWNrc3VtGAQgASgMSABSCGNoZWNrc3VtiAEBQgsKCV9jaGVja3'
'N1bRpaCgpVcGxvYWREb25lEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2FkVG9rZW4SKQoQ'
'cmVjaXBpZW50c19jb3VudBgCIAEoDVIPcmVjaXBpZW50c0NvdW50Gk0KDERvd25sb2FkRGF0YR'
'IlCg5kb3dubG9hZF90b2tlbhgBIAEoDFINZG93bmxvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1S'
'Bm9mZnNldBo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd25sb2'
'FkVG9rZW4aDwoNRGVsZXRlQWNjb3VudEIRCg9BcHBsaWNhdGlvbkRhdGE=');
@$core.Deprecated('Use responseDescriptor instead')
const Response$json = {

View file

@ -44,6 +44,7 @@ class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode PlanDowngrade = ErrorCode._(1025, _omitEnumNames ? '' : 'PlanDowngrade');
static const ErrorCode PlanUpgradeNotYearly = ErrorCode._(1026, _omitEnumNames ? '' : 'PlanUpgradeNotYearly');
static const ErrorCode InvalidSignedPreKey = ErrorCode._(1027, _omitEnumNames ? '' : 'InvalidSignedPreKey');
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
static const $core.List<ErrorCode> values = <ErrorCode> [
Unknown,
@ -76,6 +77,7 @@ class ErrorCode extends $pb.ProtobufEnum {
PlanDowngrade,
PlanUpgradeNotYearly,
InvalidSignedPreKey,
UserIdNotFound,
];
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

View file

@ -47,6 +47,7 @@ const ErrorCode$json = {
{'1': 'PlanDowngrade', '2': 1025},
{'1': 'PlanUpgradeNotYearly', '2': 1026},
{'1': 'InvalidSignedPreKey', '2': 1027},
{'1': 'UserIdNotFound', '2': 1028},
],
};
@ -65,5 +66,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
'Cg5JbnZhbGlkUHJlS2V5cxD8BxITCg5Wb3VjaGVySW5WYWxpZBD9BxITCg5QbGFuTm90QWxsb3'
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
'duZWRQcmVLZXkQgwg=');
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAg=');

View file

@ -532,6 +532,13 @@ class ApiService {
return await sendRequestSync(req);
}
Future<Result> deleteAccount() async {
var get = ApplicationData_DeleteAccount();
var appData = ApplicationData()..deleteaccount = get;
var req = createClientToServerFromApplicationData(appData);
return await sendRequestSync(req);
}
Future<Result> redeemUserInviteCode(String inviteCode) async {
var get = ApplicationData_RedeemAdditionalCode()..inviteCode = inviteCode;
var appData = ApplicationData()..redeemadditionalcode = get;

View file

@ -1,12 +1,55 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:restart_app/restart_app.dart';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/settings/account/refund_credits.view.dart';
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
class AccountView extends StatelessWidget {
class AccountView extends StatefulWidget {
const AccountView({super.key});
@override
State<AccountView> createState() => _AccountViewState();
}
class _AccountViewState extends State<AccountView> {
String? formattedBallance;
bool hasRemainingBallance = false;
@override
void initState() {
initAsync();
super.initState();
}
Future initAsync() async {
final ballance = await loadPlanBalance(useCache: false);
if (ballance == null || !mounted) return;
int ballanceInCents = ballance.transactions
.where((x) =>
(x.transactionType != Response_TransactionTypes.ThanksForTesting ||
kDebugMode))
.map((a) => a.depositCents.toInt())
.sum;
if (ballanceInCents < 0) {
ballanceInCents = 0;
}
hasRemainingBallance = (ballanceInCents > 0);
Locale myLocale = Localizations.localeOf(context);
formattedBallance = NumberFormat.currency(
locale: myLocale.toString(),
symbol: '',
decimalDigits: 2,
).format(ballanceInCents / 100);
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -19,27 +62,79 @@ class AccountView extends StatelessWidget {
title: Text("Transfer account"),
subtitle: Text("Coming soon"),
onTap: () async {
showAlertDialog(context, "Coming soon",
"This feature is not yet implemented!");
showAlertDialog(
context,
"Coming soon",
"This feature is not yet implemented!",
);
},
),
Divider(),
Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20),
child: Text(
"Danger Zone",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
),
ListTile(
title: Text(context.lang.settingsAccountDeleteAccount,
style: TextStyle(color: Colors.red)),
onTap: () async {
bool ok = await showAlertDialog(
context,
context.lang.settingsAccountDeleteModalTitle,
context.lang.settingsAccountDeleteModalBody);
if (ok) {
await deleteLocalUserData();
Restart.restartApp(
notificationTitle: 'Successfully logged out',
notificationBody: 'Click here to open the app again',
);
}
},
title: Text(
context.lang.settingsAccountDeleteAccount,
style: TextStyle(color: Colors.red),
),
subtitle: (formattedBallance == null)
? Text(context.lang.settingsAccountDeleteAccountNoInternet)
: (hasRemainingBallance)
? Text(context.lang
.settingsAccountDeleteAccountWithBallance(
formattedBallance!))
: Text(context.lang.settingsAccountDeleteAccountNoBallance),
onTap: (formattedBallance == null)
? null
: () async {
if (hasRemainingBallance) {
bool? canGoNext = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return RefundCreditsView(
formattedBalance: formattedBallance!,
);
}));
initAsync();
if (canGoNext == null || !canGoNext) return;
}
if (!context.mounted) return;
bool ok = await showAlertDialog(
context,
context.lang.settingsAccountDeleteModalTitle,
context.lang.settingsAccountDeleteModalBody,
);
if (ok) {
final res = await apiService.deleteAccount();
if (res.isError) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Could not delete the account. Please ensure you have a internet connection!",
),
duration: Duration(seconds: 3),
),
);
return;
}
await deleteLocalUserData();
Restart.restartApp(
notificationTitle: 'Account successfully deleted',
notificationBody: 'Click here to open the app again',
);
}
},
),
],
),

View file

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/views/settings/subscription/voucher.view.dart';
import 'package:url_launcher/url_launcher.dart';
class RefundCreditsView extends StatefulWidget {
const RefundCreditsView({super.key, required this.formattedBalance});
final String formattedBalance;
@override
State<RefundCreditsView> createState() => _RefundCreditsViewState();
}
class _RefundCreditsViewState extends State<RefundCreditsView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Refund Credits'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
'Remaining balance: ${widget.formattedBalance}',
textAlign: TextAlign.center,
),
),
const SizedBox(height: 20), // Space between balance and options
ListTile(
title: Text("Create a Voucher"),
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return VoucherView();
}));
Navigator.pop(context, false);
},
),
ListTile(
title: Text("Spend to an Open Source Project"),
onTap: () async {},
),
ListTile(
title: Text("Spend to an NGO"),
onTap: () async {},
),
ListTile(
title: Text("Spend to twonly"),
onTap: () async {},
),
Divider(),
ListTile(
title: Text(
"Learn more about your donation",
),
subtitle: Text(
"This will open our webpage which will provide you more informations where we will donate your remaining ballance if you choose this option.",
),
onTap: () {
launchUrl(Uri.parse("https://twonly.eu/de/donation/"));
},
trailing: FaIcon(
FontAwesomeIcons.arrowUpRightFromSquare,
size: 15,
),
),
],
),
),
);
}
}

View file

@ -57,7 +57,7 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
Future initAsync(bool force) async {
additionalInvites = await loadAdditionalUserInvites();
if (force) {
ballance = await loadPlanBallance();
ballance = await loadPlanBalance();
}
setState(() {});
}

View file

@ -35,7 +35,7 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
Future initAsync(bool force) async {
if (force) {
ballance = await loadPlanBallance();
ballance = await loadPlanBalance();
if (ballance != null) {
autoRenewal = ballance!.autoRenewal;
}
@ -90,12 +90,6 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
},
),
),
// SizedBox(height: 20),
// Divider(),
// ListTile(
// title: Text("Cancel subscription"),
// onTap: () async {},
// ),
],
),
);

View file

@ -10,12 +10,13 @@ import 'package:twonly/src/views/settings/subscription/voucher.view.dart';
import 'package:url_launcher/url_launcher.dart';
class SelectPaymentView extends StatefulWidget {
const SelectPaymentView(
{super.key,
this.planId,
this.payMonthly,
this.valueInCents,
this.refund});
const SelectPaymentView({
super.key,
this.planId,
this.payMonthly,
this.valueInCents,
this.refund,
});
final String? planId;
final bool? payMonthly;
@ -33,7 +34,7 @@ enum PaymentMethods {
}
class _SelectPaymentViewState extends State<SelectPaymentView> {
int? ballanceInCents;
int? balanceInCents;
int checkoutInCents = 0;
bool tryAutoRenewal = true;
@ -47,9 +48,9 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
}
Future initAsync() async {
final ballance = await loadPlanBallance();
ballanceInCents =
ballance!.transactions.map((a) => a.depositCents.toInt()).sum;
final balance = await loadPlanBalance();
balanceInCents =
balance!.transactions.map((a) => a.depositCents.toInt()).sum;
setState(() {});
}
@ -74,7 +75,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
? "${localePrizing(context, checkoutInCents)}/${(widget.payMonthly!) ? context.lang.month : context.lang.year}"
: localePrizing(context, checkoutInCents);
bool canPay = (paymentMethods == PaymentMethods.twonlyCredit &&
(ballanceInCents == null || ballanceInCents! >= checkoutInCents));
(balanceInCents == null || balanceInCents! >= checkoutInCents));
return Scaffold(
appBar: AppBar(
title: Text(context.lang.selectPaymentMethode),
@ -98,9 +99,9 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.lang.twonlyCredit),
if (ballanceInCents != null)
if (balanceInCents != null)
Text(
"${context.lang.currentBalance}: ${localePrizing(context, ballanceInCents!)}",
"${context.lang.currentBalance}: ${localePrizing(context, balanceInCents!)}",
style: TextStyle(fontSize: 10),
)
],

View file

@ -35,7 +35,7 @@ String localePrizing(BuildContext context, int cents) {
).format(cents / 100);
}
Future<Response_PlanBallance?> loadPlanBallance() async {
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
Response_PlanBallance? ballance;
final user = await getUser();
if (user == null) return ballance;
@ -43,7 +43,7 @@ Future<Response_PlanBallance?> loadPlanBallance() async {
if (ballance != null) {
user.lastPlanBallance = ballance.writeToJson();
await updateUser(user);
} else if (user.lastPlanBallance != null) {
} else if (user.lastPlanBallance != null && useCache) {
try {
ballance = Response_PlanBallance.fromJson(
user.lastPlanBallance!,
@ -113,7 +113,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
}
Future initAsync() async {
ballance = await loadPlanBallance();
ballance = await loadPlanBalance();
if (ballance != null && ballance!.hasAdditionalAccountOwnerId()) {
final ownerId = ballance!.additionalAccountOwnerId.toInt();
Contact? contact = await twonlyDB.contactsDao