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
- 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.
- iOS gestures to close images
- Improved chat messages view, including better citation view and display times
- onboarding screens updated and registration view simplified
- The sender is displayed in the top right corner when a media file is opened
- Images are now stored as WebP to save storage
- Button to report users
- Multiple bug fixes

View file

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

View file

@ -331,5 +331,8 @@
"doubleClickToReopen": "Doppelklicken zum\nerneuten Öffnen.",
"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!",
"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",
"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!",
"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:
/// **'Open changelog automatically'**
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

View file

@ -1076,4 +1076,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override
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
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': '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': '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': [
{'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')
const ApplicationData_DeleteAccount$json = {
'1': 'DeleteAccount',
@ -351,26 +361,29 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'cHJla2V5Ynl1c2VyaWQSZgoSdXBkYXRlc2lnbmVkcHJla2V5GBcgASgLMjQuY2xpZW50X3RvX3'
'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVkUHJlS2V5SABSEnVwZGF0ZXNpZ25l'
'ZHByZWtleRJXCg1kZWxldGVhY2NvdW50GBggASgLMi8uY2xpZW50X3RvX3NlcnZlci5BcHBsaW'
'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVhY2NvdW50GmoKC1RleHRNZXNzYWdl'
'EhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZG'
'F0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJu'
'YW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbh'
'IdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9p'
'ZBgBIAEoA1IGdXNlcklkGikKDVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91Y2'
'hlchpwChFTd2l0Y2hUb1BheWVkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcGF5'
'X21vbnRobHkYAiABKAhSCnBheU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRvUm'
'VuZXdhbBo2ChFVcGRhdGVQbGFuT3B0aW9ucxIhCgxhdXRvX3JlbmV3YWwYASABKAhSC2F1dG9S'
'ZW5ld2FsGjAKDUNyZWF0ZVZvdWNoZXISHwoLdmFsdWVfY2VudHMYASABKA1SCnZhbHVlQ2VudH'
'MaDQoLR2V0TG9jYXRpb24aDQoLR2V0Vm91Y2hlcnMaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoV'
'R2V0QWRkQWNjb3VudHNJbnZpdGVzGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaNwoUUmVkZWVtQW'
'RkaXRpb25hbENvZGUSHwoLaW52aXRlX2NvZGUYAiABKAlSCmludml0ZUNvZGUaLwoUUmVtb3Zl'
'QWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeV'
'VzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2Vy'
'SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2'
'lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgC'
'IAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaW'
'duZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEg'
'ASgMUg1kb3dubG9hZFRva2VuGg8KDURlbGV0ZUFjY291bnRCEQoPQXBwbGljYXRpb25EYXRh');
'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVhY2NvdW50Ek4KCnJlcG9ydHVzZXIY'
'GSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcnRVc2VySABSCn'
'JlcG9ydHVzZXIaagoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJv'
'ZHkYAyABKAxSBGJvZHkSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdX'
'NoX2RhdGEaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1l'
'GjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbR'
'omCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaKQoNUmVkZWVtVm91Y2hl'
'chIYCgd2b3VjaGVyGAEgASgJUgd2b3VjaGVyGnAKEVN3aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW'
'5faWQYASABKAlSBnBsYW5JZBIfCgtwYXlfbW9udGhseRgCIAEoCFIKcGF5TW9udGhseRIhCgxh'
'dXRvX3JlbmV3YWwYAyABKAhSC2F1dG9SZW5ld2FsGjYKEVVwZGF0ZVBsYW5PcHRpb25zEiEKDG'
'F1dG9fcmVuZXdhbBgBIAEoCFILYXV0b1JlbmV3YWwaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1'
'ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW50cxoNCgtHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycx'
'oTChFHZXRBdmFpbGFibGVQbGFucxoXChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3Vy'
'cmVudFBsYW5JbmZvcxo3ChRSZWRlZW1BZGRpdGlvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIA'
'EoCVIKaW52aXRlQ29kZRovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgD'
'UgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZB'
'oyChdHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEK'
'ElVwZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcm'
'VrZXlJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3By'
'ZWtleV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZE'
'RvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNl'
'chIoChByZXBvcnRlZF91c2VyX2lkGAEgASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAi'
'ABKAlSBnJlYXNvbhoPCg1EZWxldGVBY2NvdW50QhEKD0FwcGxpY2F0aW9uRGF0YQ==');
@$core.Deprecated('Use responseDescriptor instead')
const Response$json = {

View file

@ -576,6 +576,15 @@ class ApiService {
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 {
final get = ApplicationData_DeleteAccount();
final appData = ApplicationData()..deleteaccount = get;

View file

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

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
Widget build(BuildContext context) {
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(
icon: FontAwesomeIcons.ban,
color: Colors.red,
text: context.lang.contactBlock,
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 monthlyPrice = getPlanPrice(planId, paidMonthly: true);
var features = <String>[];
final isPayingUser = planId == 'Family' || planId == 'Pro';
switch (planId) {
case 'Free':
@ -419,7 +418,7 @@ class PlanCard extends StatelessWidget {
),
),
if (yearlyPrice != 0) const SizedBox(height: 10),
if (isPayingUser)
if (yearlyPrice != 0 && paidMonthly == null)
Column(
children: [
if (paidMonthly == null || paidMonthly!)
@ -442,7 +441,7 @@ class PlanCard extends StatelessWidget {
),
],
),
if (isPayingUser && paidMonthly != null)
if (paidMonthly != null)
Text(
(paidMonthly!)
? '${localePrizing(context, monthlyPrice)}/${context.lang.month}'