This commit is contained in:
otsmr 2025-07-18 22:39:10 +02:00
parent ad8dea4fbf
commit 6d1643848f
13 changed files with 1025 additions and 1473 deletions

View file

@ -3,9 +3,10 @@
## 0.0.58 ## 0.0.58
- twonly now has a free plan and is now financed by donations and an optional subscription with more features (coming soon) - 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 - iOS gestures to close images
- Improved the chat messages view, including better citation view and display times. - Improved chat messages view, including better citation view and display times
- Updated onboarding screens and simplified the registration view - onboarding screens updated and registration view simplified
- The sender is displayed in the top right corner when a media file is opened. - The sender is displayed in the top right corner when a media file is opened
- Images are now stored as WebP to save storage - Images are now stored as WebP to save storage
- Button to report users
- Multiple bug fixes - Multiple bug fixes

View file

@ -219,6 +219,9 @@ String getContactDisplayName(Contact user) {
if (user.deleted) { if (user.deleted) {
name = applyStrikethrough(name); name = applyStrikethrough(name);
} }
if (name.length > 12) {
return '${name.substring(0, 12)} ...';
}
return name; return name;
} }

View file

@ -331,5 +331,8 @@
"doubleClickToReopen": "Doppelklicken zum\nerneuten Öffnen.", "doubleClickToReopen": "Doppelklicken zum\nerneuten Öffnen.",
"retransmissionRequested": "Wird erneut versucht.", "retransmissionRequested": "Wird erneut versucht.",
"testPaymentMethod": "Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!", "testPaymentMethod": "Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!",
"openChangeLog": "Changelog automatisch öffnen" "openChangeLog": "Changelog automatisch öffnen",
"reportUserTitle": "Melde {username}",
"reportUserReason": "Meldegrund",
"reportUser": "Benutzer melden"
} }

View file

@ -487,5 +487,8 @@
"doubleClickToReopen": "Double-click\nto open again", "doubleClickToReopen": "Double-click\nto open again",
"retransmissionRequested": "Retransmission requested", "retransmissionRequested": "Retransmission requested",
"testPaymentMethod": "Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!", "testPaymentMethod": "Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!",
"openChangeLog": "Open changelog automatically" "openChangeLog": "Open changelog automatically",
"reportUserTitle": "Report {username}",
"reportUserReason": "Reporting reason",
"reportUser": "Report user"
} }

View file

@ -2029,6 +2029,24 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Open changelog automatically'** /// **'Open changelog automatically'**
String get openChangeLog; String get openChangeLog;
/// No description provided for @reportUserTitle.
///
/// In en, this message translates to:
/// **'Report {username}'**
String reportUserTitle(Object username);
/// No description provided for @reportUserReason.
///
/// In en, this message translates to:
/// **'Reporting reason'**
String get reportUserReason;
/// No description provided for @reportUser.
///
/// In en, this message translates to:
/// **'Report user'**
String get reportUser;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View file

@ -1076,4 +1076,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get openChangeLog => 'Changelog automatisch öffnen'; String get openChangeLog => 'Changelog automatisch öffnen';
@override
String reportUserTitle(Object username) {
return 'Melde $username';
}
@override
String get reportUserReason => 'Meldegrund';
@override
String get reportUser => 'Benutzer melden';
} }

View file

@ -1070,4 +1070,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get openChangeLog => 'Open changelog automatically'; String get openChangeLog => 'Open changelog automatically';
@override
String reportUserTitle(Object username) {
return 'Report $username';
}
@override
String get reportUserReason => 'Reporting reason';
@override
String get reportUser => 'Report user';
} }

File diff suppressed because it is too large Load diff

View file

@ -156,8 +156,9 @@ const ApplicationData$json = {
{'1': 'getsignedprekeybyuserid', '3': 22, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetSignedPreKeyByUserId', '9': 0, '10': 'getsignedprekeybyuserid'}, {'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': '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'}, {'1': 'deleteaccount', '3': 24, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.DeleteAccount', '9': 0, '10': 'deleteaccount'},
{'1': 'reportuser', '3': 25, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.ReportUser', '9': 0, '10': 'reportuser'},
], ],
'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_DownloadDone$json, ApplicationData_DeleteAccount$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_DownloadDone$json, ApplicationData_ReportUser$json, ApplicationData_DeleteAccount$json],
'8': [ '8': [
{'1': 'ApplicationData'}, {'1': 'ApplicationData'},
], ],
@ -309,6 +310,15 @@ const ApplicationData_DownloadDone$json = {
], ],
}; };
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_ReportUser$json = {
'1': 'ReportUser',
'2': [
{'1': 'reported_user_id', '3': 1, '4': 1, '5': 3, '10': 'reportedUserId'},
{'1': 'reason', '3': 2, '4': 1, '5': 9, '10': 'reason'},
],
};
@$core.Deprecated('Use applicationDataDescriptor instead') @$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_DeleteAccount$json = { const ApplicationData_DeleteAccount$json = {
'1': 'DeleteAccount', '1': 'DeleteAccount',
@ -351,26 +361,29 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'cHJla2V5Ynl1c2VyaWQSZgoSdXBkYXRlc2lnbmVkcHJla2V5GBcgASgLMjQuY2xpZW50X3RvX3' 'cHJla2V5Ynl1c2VyaWQSZgoSdXBkYXRlc2lnbmVkcHJla2V5GBcgASgLMjQuY2xpZW50X3RvX3'
'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVkUHJlS2V5SABSEnVwZGF0ZXNpZ25l' 'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVkUHJlS2V5SABSEnVwZGF0ZXNpZ25l'
'ZHByZWtleRJXCg1kZWxldGVhY2NvdW50GBggASgLMi8uY2xpZW50X3RvX3NlcnZlci5BcHBsaW' 'ZHByZWtleRJXCg1kZWxldGVhY2NvdW50GBggASgLMi8uY2xpZW50X3RvX3NlcnZlci5BcHBsaW'
'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVhY2NvdW50GmoKC1RleHRNZXNzYWdl' 'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVhY2NvdW50Ek4KCnJlcG9ydHVzZXIY'
'EhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZG' 'GSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcnRVc2VySABSCn'
'F0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJu' 'JlcG9ydHVzZXIaagoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJv'
'YW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbh' 'ZHkYAyABKAxSBGJvZHkSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdX'
'IdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9p' 'NoX2RhdGEaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1l'
'ZBgBIAEoA1IGdXNlcklkGikKDVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2' 'GjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbR'
'hlchpwChFTd2l0Y2hUb1BheWVkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5' 'omCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaKQoNUmVkZWVtVm91Y2hl'
'X21vbnRobHkYAiABKAhSCnBheU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUm' 'chIYCgd2b3VjaGVyGAEgASgJUgd2b3VjaGVyGnAKEVN3aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW'
'VuZXdhbBo2ChFVcGRhdGVQbGFuT3B0aW9ucxIhCgxhdXRvX3JlbmV3YWwYASABKAhSC2F1dG9S' '5faWQYASABKAlSBnBsYW5JZBIfCgtwYXlfbW9udGhseRgCIAEoCFIKcGF5TW9udGhseRIhCgxh'
'ZW5ld2FsGjAKDUNyZWF0ZVZvdWNoZXISHwoLdmFsdWVfY2VudHMYASABKA1SCnZhbHVlQ2VudH' 'dXRvX3JlbmV3YWwYAyABKAhSC2F1dG9SZW5ld2FsGjYKEVVwZGF0ZVBsYW5PcHRpb25zEiEKDG'
'MaDQoLR2V0TG9jYXRpb24aDQoLR2V0Vm91Y2hlcnMaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoV' 'F1dG9fcmVuZXdhbBgBIAEoCFILYXV0b1JlbmV3YWwaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1'
'R2V0QWRkQWNjb3VudHNJbnZpdGVzGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaNwoUUmVkZWVtQW' 'ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW50cxoNCgtHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycx'
'RkaXRpb25hbENvZGUSHwoLaW52aXRlX2NvZGUYAiABKAlSCmludml0ZUNvZGUaLwoUUmVtb3Zl' 'oTChFHZXRBdmFpbGFibGVQbGFucxoXChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3Vy'
'QWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeV' 'cmVudFBsYW5JbmZvcxo3ChRSZWRlZW1BZGRpdGlvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIA'
'VzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2Vy' 'EoCVIKaW52aXRlQ29kZRovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgD'
'SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2' 'UgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZB'
'lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgC' 'oyChdHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEK'
'IAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaW' 'ElVwZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcm'
'duZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEg' 'VrZXlJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3By'
'ASgMUg1kb3dubG9hZFRva2VuGg8KDURlbGV0ZUFjY291bnRCEQoPQXBwbGljYXRpb25EYXRh'); 'ZWtleV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZE'
'RvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNl'
'chIoChByZXBvcnRlZF91c2VyX2lkGAEgASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAi'
'ABKAlSBnJlYXNvbhoPCg1EZWxldGVBY2NvdW50QhEKD0FwcGxpY2F0aW9uRGF0YQ==');
@$core.Deprecated('Use responseDescriptor instead') @$core.Deprecated('Use responseDescriptor instead')
const Response$json = { const Response$json = {

View file

@ -576,6 +576,15 @@ class ApiService {
return sendRequestSync(req); return sendRequestSync(req);
} }
Future<Result> reportUser(int userId, String reason) async {
final get = ApplicationData_ReportUser()
..reportedUserId = Int64(userId)
..reason = reason;
final appData = ApplicationData()..reportuser = get;
final req = createClientToServerFromApplicationData(appData);
return sendRequestSync(req);
}
Future<Result> deleteAccount() async { Future<Result> deleteAccount() async {
final get = ApplicationData_DeleteAccount(); final get = ApplicationData_DeleteAccount();
final appData = ApplicationData()..deleteaccount = get; final appData = ApplicationData()..deleteaccount = get;

View file

@ -317,9 +317,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
return; return;
} }
widget.selectedCameraDetails.scaleFactor = widget.selectedCameraDetails.scaleFactor = (baseScaleFactor +
// ignore: avoid_dynamic_calls // ignore: avoid_dynamic_calls
(baseScaleFactor + (basePanY - (details.localPosition.dy as int)) / 30) (basePanY - (details.localPosition.dy as double)) / 30)
.clamp(1, widget.selectedCameraDetails.maxAvailableZoom); .clamp(1, widget.selectedCameraDetails.maxAvailableZoom);
await widget.cameraController! await widget.cameraController!

View file

@ -56,6 +56,38 @@ class _ContactViewState extends State<ContactView> {
} }
} }
Future<void> handleReportUser(Contact contact) async {
final reason = await showReportDialog(context, contact);
if (reason == null) return;
final res = await apiService.reportUser(contact.userId, reason);
if (!mounted) return;
if (res.isSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Benutzer wurde gemeldet.'),
duration: Duration(seconds: 3),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Es ist ein Fehler aufgetreten. Bitte versuche es später erneut.'),
duration: Duration(seconds: 3),
),
);
}
// if (block) {
// const update = ContactsCompanion(blocked: Value(true));
// if (context.mounted) {
// await twonlyDB.contactsDao.updateContact(contact.userId, update);
// }
// if (mounted) {
// Navigator.popUntil(context, (route) => route.isFirst);
// }
// }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final contact = twonlyDB.contactsDao final contact = twonlyDB.contactsDao
@ -146,9 +178,13 @@ class _ContactViewState extends State<ContactView> {
} }
}, },
), ),
BetterListTile(
icon: FontAwesomeIcons.flag,
text: context.lang.reportUser,
onTap: () => handleReportUser(contact),
),
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.ban, icon: FontAwesomeIcons.ban,
color: Colors.red,
text: context.lang.contactBlock, text: context.lang.contactBlock,
onTap: () => handleUserBlockRequest(contact), onTap: () => handleUserBlockRequest(contact),
), ),
@ -202,3 +238,39 @@ Future<String?> showNicknameChangeDialog(
}, },
); );
} }
Future<String?> showReportDialog(
BuildContext context,
Contact contact,
) {
final controller = TextEditingController();
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title:
Text(context.lang.reportUserTitle(getContactDisplayName(contact))),
content: TextField(
controller: controller,
autofocus: true,
decoration: InputDecoration(hintText: context.lang.reportUserReason),
),
actions: <Widget>[
TextButton(
child: Text(context.lang.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(context.lang.ok),
onPressed: () {
Navigator.of(context).pop(controller.text);
},
),
],
);
},
);
}

View file

@ -371,7 +371,6 @@ class PlanCard extends StatelessWidget {
final yearlyPrice = getPlanPrice(planId, paidMonthly: false); final yearlyPrice = getPlanPrice(planId, paidMonthly: false);
final monthlyPrice = getPlanPrice(planId, paidMonthly: true); final monthlyPrice = getPlanPrice(planId, paidMonthly: true);
var features = <String>[]; var features = <String>[];
final isPayingUser = planId == 'Family' || planId == 'Pro';
switch (planId) { switch (planId) {
case 'Free': case 'Free':
@ -419,7 +418,7 @@ class PlanCard extends StatelessWidget {
), ),
), ),
if (yearlyPrice != 0) const SizedBox(height: 10), if (yearlyPrice != 0) const SizedBox(height: 10),
if (isPayingUser) if (yearlyPrice != 0 && paidMonthly == null)
Column( Column(
children: [ children: [
if (paidMonthly == null || paidMonthly!) if (paidMonthly == null || paidMonthly!)
@ -442,7 +441,7 @@ class PlanCard extends StatelessWidget {
), ),
], ],
), ),
if (isPayingUser && paidMonthly != null) if (paidMonthly != null)
Text( Text(
(paidMonthly!) (paidMonthly!)
? '${localePrizing(context, monthlyPrice)}/${context.lang.month}' ? '${localePrizing(context, monthlyPrice)}/${context.lang.month}'