From c18a8ed311e7178cecebad286134ba19bcb89948 Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 18 Jul 2025 15:13:17 +0200 Subject: [PATCH] fix #257 --- CHANGELOG.md | 2 +- lib/app.dart | 5 + lib/src/localization/app_de.arb | 3 +- lib/src/localization/app_en.arb | 3 +- .../generated/app_localizations.dart | 6 + .../generated/app_localizations_de.dart | 3 + .../generated/app_localizations_en.dart | 3 + lib/src/model/json/userdata.dart | 3 + lib/src/model/json/userdata.g.dart | 2 + lib/src/views/chats/chat_list.view.dart | 26 +++- lib/src/views/settings/appearance.view.dart | 2 +- .../views/settings/data_and_storage.view.dart | 2 +- .../views/settings/help/changelog.view.dart | 129 ++++++++++++++++++ lib/src/views/settings/help/help.view.dart | 10 ++ .../manage_subscription.view.dart | 4 +- pubspec.yaml | 1 + 16 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 lib/src/views/settings/help/changelog.view.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b73c4c..c64f0b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.0.58 (WIP) +## 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 diff --git a/lib/app.dart b/lib/app.dart index 0de5d7d..a2a7fc2 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,5 +1,9 @@ import 'dart:async'; +import 'dart:io'; +import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; @@ -12,6 +16,7 @@ import 'package:twonly/src/views/components/app_outdated.dart'; import 'package:twonly/src/views/home.view.dart'; import 'package:twonly/src/views/onboarding/onboarding.view.dart'; import 'package:twonly/src/views/onboarding/register.view.dart'; +import 'package:twonly/src/views/settings/help/changelog.view.dart'; class App extends StatefulWidget { const App({super.key}); diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 24e05e4..1ebae28 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -330,5 +330,6 @@ "appOutdatedBtn": "Jetzt aktualisieren.", "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!" + "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" } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index fb3e4b5..a30d01a 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -486,5 +486,6 @@ "appOutdatedBtn": "Update Now", "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!" + "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" } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 33ff2f4..01473c6 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2023,6 +2023,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'** String get testPaymentMethod; + + /// No description provided for @openChangeLog. + /// + /// In en, this message translates to: + /// **'Open changelog automatically'** + String get openChangeLog; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index c5d67e0..86e6a43 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1073,4 +1073,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get testPaymentMethod => 'Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!'; + + @override + String get openChangeLog => 'Changelog automatisch öffnen'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 6d1044c..169bc68 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1067,4 +1067,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get testPaymentMethod => 'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'; + + @override + String get openChangeLog => 'Open changelog automatically'; } diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart index ebecc93..bdf9a5c 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.dart @@ -77,6 +77,9 @@ class UserData { List? lastChangeLogHash; + @JsonKey(defaultValue: false) + bool hideChangeLog = false; + // --- BACKUP --- DateTime? nextTimeToShowBackupNotice; diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart index 040f438..6303414 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.g.dart @@ -56,6 +56,7 @@ UserData _$UserDataFromJson(Map json) => UserData( ..lastChangeLogHash = (json['lastChangeLogHash'] as List?) ?.map((e) => (e as num).toInt()) .toList() + ..hideChangeLog = json['hideChangeLog'] as bool? ?? false ..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null ? null : DateTime.parse(json['nextTimeToShowBackupNotice'] as String) @@ -95,6 +96,7 @@ Map _$UserDataToJson(UserData instance) => { 'currentPreKeyIndexStart': instance.currentPreKeyIndexStart, 'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart, 'lastChangeLogHash': instance.lastChangeLogHash, + 'hideChangeLog': instance.hideChangeLog, 'nextTimeToShowBackupNotice': instance.nextTimeToShowBackupNotice?.toIso8601String(), 'backupServer': instance.backupServer, diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index a93bd09..38cb4b5 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -1,6 +1,9 @@ import 'dart:async'; +import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; @@ -24,6 +27,7 @@ import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/message_send_state_icon.dart'; import 'package:twonly/src/views/components/notification_badge.dart'; import 'package:twonly/src/views/components/user_context_menu.dart'; +import 'package:twonly/src/views/settings/help/changelog.view.dart'; import 'package:twonly/src/views/settings/help/contact_us.view.dart'; import 'package:twonly/src/views/settings/settings_main.view.dart'; import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; @@ -71,10 +75,26 @@ class _ChatListViewState extends State { }); final user = await getUser(); - if (user != null) { - setState(() { - showFeedbackShortcut = user.showFeedbackShortcut; + if (user == null) return; + setState(() { + showFeedbackShortcut = user.showFeedbackShortcut; + }); + + final changeLog = await rootBundle.loadString('CHANGELOG.md'); + final changeLogHash = + (await compute(Sha256().hash, changeLog.codeUnits)).bytes; + if (!user.hideChangeLog && + user.lastChangeLogHash.toString() != changeLogHash.toString()) { + await updateUserdata((u) { + u.lastChangeLogHash = changeLogHash; + return u; }); + if (!mounted) return; + await Navigator.push(context, MaterialPageRoute(builder: (context) { + return ChangeLogView( + changeLog: changeLog, + ); + })); } } diff --git a/lib/src/views/settings/appearance.view.dart b/lib/src/views/settings/appearance.view.dart index 153b8f5..00685a4 100644 --- a/lib/src/views/settings/appearance.view.dart +++ b/lib/src/views/settings/appearance.view.dart @@ -108,7 +108,7 @@ class _AppearanceViewState extends State { ListTile( title: Text(context.lang.contactUsShortcut), onTap: toggleShowFeedbackIcon, - trailing: Checkbox( + trailing: Switch( value: !showFeedbackShortcut, onChanged: (a) => toggleShowFeedbackIcon(), ), diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 9373640..559c50b 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -68,7 +68,7 @@ class _DataAndStorageViewState extends State { title: Text(context.lang.settingsStorageDataStoreInGTitle), subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle), onTap: toggleStoreInGallery, - trailing: Checkbox( + trailing: Switch( value: storeMediaFilesInGallery, onChanged: (a) => toggleStoreInGallery(), ), diff --git a/lib/src/views/settings/help/changelog.view.dart b/lib/src/views/settings/help/changelog.view.dart new file mode 100644 index 0000000..adf7b2b --- /dev/null +++ b/lib/src/views/settings/help/changelog.view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/utils/storage.dart'; + +List parseMarkdown(BuildContext context, String markdown) { + List widgets = []; + // Split the string into lines + final lines = markdown.split('\n'); + + for (var line in lines) { + line = line.trim(); + + // Check for headers + if (line.startsWith('# ')) { + // widgets.add(Padding( + // padding: const EdgeInsets.all(8), + // child: Text( + // line.substring(2), // Remove the '# ' part + // textAlign: TextAlign.center, + // style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + // ), + // )); + } else if (line.startsWith('## ')) { + widgets.add(Padding( + padding: const EdgeInsets.only(top: 20, bottom: 10), + child: Text( + line.substring(3), // Remove the '## ' part + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + )); + } + // Check for bullet points + else if (line.startsWith('- ')) { + widgets.add(Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 7), + child: Icon( + Icons.brightness_1, + size: 7, + color: context.color.onSurface, + ), + ), // Bullet point icon + const SizedBox(width: 8), // Space between bullet and text + Expanded(child: Text(line.substring(2))), // Remove the '- ' part + ], + )); + } else { + widgets.add(Text(line)); + } + } + + return widgets; +} + +class ChangeLogView extends StatefulWidget { + const ChangeLogView({super.key, this.changeLog}); + + final String? changeLog; + + @override + State createState() => _ChangeLogViewState(); +} + +class _ChangeLogViewState extends State { + String changeLog = ''; + bool hideChangeLog = false; + + @override + void initState() { + super.initState(); + if (widget.changeLog != null) { + changeLog = widget.changeLog!; + } else { + initAsync(); + } + } + + Future initAsync() async { + changeLog = await rootBundle.loadString('CHANGELOG.md'); + final user = await getUser(); + if (user != null) { + hideChangeLog = user.hideChangeLog; + } + setState(() {}); + } + + Future _toggleAutoOpen(bool value) async { + await updateUserdata((u) { + u.hideChangeLog = !hideChangeLog; + return u; + }); + setState(() { + hideChangeLog = !value; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Changelog'), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: ListView( + children: parseMarkdown(context, changeLog), + ), + ), + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(context.lang.openChangeLog), + Switch( + value: !hideChangeLog, + onChanged: _toggleAutoOpen, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/views/settings/help/help.view.dart b/lib/src/views/settings/help/help.view.dart index 87c3a04..e2ae4d8 100644 --- a/lib/src/views/settings/help/help.view.dart +++ b/lib/src/views/settings/help/help.view.dart @@ -5,6 +5,7 @@ import 'package:twonly/globals.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/help/changelog.view.dart'; import 'package:twonly/src/views/settings/help/contact_us.view.dart'; import 'package:twonly/src/views/settings/help/credits.view.dart'; import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; @@ -91,6 +92,15 @@ class HelpView extends StatelessWidget { })); }, ), + ListTile( + title: const Text('Changelog'), + onTap: () async { + await Navigator.push(context, + MaterialPageRoute(builder: (context) { + return const ChangeLogView(); + })); + }, + ), ListTile( title: const Text("Open Source"), onTap: () { diff --git a/lib/src/views/settings/subscription/manage_subscription.view.dart b/lib/src/views/settings/subscription/manage_subscription.view.dart index 5e79bbe..3e2a0db 100644 --- a/lib/src/views/settings/subscription/manage_subscription.view.dart +++ b/lib/src/views/settings/subscription/manage_subscription.view.dart @@ -87,8 +87,8 @@ class _ManageSubscriptionViewState extends State { style: const TextStyle(fontSize: 12), ), onTap: toggleRenewalOption, - trailing: Checkbox( - value: autoRenewal, + trailing: Switch( + value: autoRenewal!, onChanged: (a) { toggleRenewalOption(); }, diff --git a/pubspec.yaml b/pubspec.yaml index b350f09..5ccb372 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,4 +104,5 @@ flutter: - assets/animated_icons/ - assets/animations/ - assets/passwords/ + - CHANGELOG.md