mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
fix #257
This commit is contained in:
parent
afb1806cb1
commit
c18a8ed311
16 changed files with 194 additions and 10 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Changelog
|
# 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)
|
- 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
|
- Implementing iOS gestures to close images
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import 'dart:async';
|
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/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.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/home.view.dart';
|
||||||
import 'package:twonly/src/views/onboarding/onboarding.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/onboarding/register.view.dart';
|
||||||
|
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
|
|
|
||||||
|
|
@ -330,5 +330,6 @@
|
||||||
"appOutdatedBtn": "Jetzt aktualisieren.",
|
"appOutdatedBtn": "Jetzt aktualisieren.",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|
@ -486,5 +486,6 @@
|
||||||
"appOutdatedBtn": "Update Now",
|
"appOutdatedBtn": "Update Now",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|
@ -2023,6 +2023,12 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// 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!'**
|
/// **'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'**
|
||||||
String get testPaymentMethod;
|
String get testPaymentMethod;
|
||||||
|
|
||||||
|
/// No description provided for @openChangeLog.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Open changelog automatically'**
|
||||||
|
String get openChangeLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1073,4 +1073,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get testPaymentMethod =>
|
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!';
|
'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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1067,4 +1067,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get testPaymentMethod =>
|
String get testPaymentMethod =>
|
||||||
'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!';
|
'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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ class UserData {
|
||||||
|
|
||||||
List<int>? lastChangeLogHash;
|
List<int>? lastChangeLogHash;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool hideChangeLog = false;
|
||||||
|
|
||||||
// --- BACKUP ---
|
// --- BACKUP ---
|
||||||
|
|
||||||
DateTime? nextTimeToShowBackupNotice;
|
DateTime? nextTimeToShowBackupNotice;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..lastChangeLogHash = (json['lastChangeLogHash'] as List<dynamic>?)
|
..lastChangeLogHash = (json['lastChangeLogHash'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num).toInt())
|
?.map((e) => (e as num).toInt())
|
||||||
.toList()
|
.toList()
|
||||||
|
..hideChangeLog = json['hideChangeLog'] as bool? ?? false
|
||||||
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
||||||
|
|
@ -95,6 +96,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
|
'hideChangeLog': instance.hideChangeLog,
|
||||||
'nextTimeToShowBackupNotice':
|
'nextTimeToShowBackupNotice':
|
||||||
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
||||||
'backupServer': instance.backupServer,
|
'backupServer': instance.backupServer,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.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/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/views/components/notification_badge.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/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/help/contact_us.view.dart';
|
||||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||||
|
|
@ -71,10 +75,26 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null) {
|
if (user == null) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
showFeedbackShortcut = user.showFeedbackShortcut;
|
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,
|
||||||
|
);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.contactUsShortcut),
|
title: Text(context.lang.contactUsShortcut),
|
||||||
onTap: toggleShowFeedbackIcon,
|
onTap: toggleShowFeedbackIcon,
|
||||||
trailing: Checkbox(
|
trailing: Switch(
|
||||||
value: !showFeedbackShortcut,
|
value: !showFeedbackShortcut,
|
||||||
onChanged: (a) => toggleShowFeedbackIcon(),
|
onChanged: (a) => toggleShowFeedbackIcon(),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
title: Text(context.lang.settingsStorageDataStoreInGTitle),
|
title: Text(context.lang.settingsStorageDataStoreInGTitle),
|
||||||
subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle),
|
subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle),
|
||||||
onTap: toggleStoreInGallery,
|
onTap: toggleStoreInGallery,
|
||||||
trailing: Checkbox(
|
trailing: Switch(
|
||||||
value: storeMediaFilesInGallery,
|
value: storeMediaFilesInGallery,
|
||||||
onChanged: (a) => toggleStoreInGallery(),
|
onChanged: (a) => toggleStoreInGallery(),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
129
lib/src/views/settings/help/changelog.view.dart
Normal file
129
lib/src/views/settings/help/changelog.view.dart
Normal file
|
|
@ -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<Widget> parseMarkdown(BuildContext context, String markdown) {
|
||||||
|
List<Widget> 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<ChangeLogView> createState() => _ChangeLogViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeLogViewState extends State<ChangeLogView> {
|
||||||
|
String changeLog = '';
|
||||||
|
bool hideChangeLog = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.changeLog != null) {
|
||||||
|
changeLog = widget.changeLog!;
|
||||||
|
} else {
|
||||||
|
initAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initAsync() async {
|
||||||
|
changeLog = await rootBundle.loadString('CHANGELOG.md');
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null) {
|
||||||
|
hideChangeLog = user.hideChangeLog;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.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/contact_us.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/credits.view.dart';
|
import 'package:twonly/src/views/settings/help/credits.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/diagnostics.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(
|
ListTile(
|
||||||
title: const Text("Open Source"),
|
title: const Text("Open Source"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
onTap: toggleRenewalOption,
|
onTap: toggleRenewalOption,
|
||||||
trailing: Checkbox(
|
trailing: Switch(
|
||||||
value: autoRenewal,
|
value: autoRenewal!,
|
||||||
onChanged: (a) {
|
onChanged: (a) {
|
||||||
toggleRenewalOption();
|
toggleRenewalOption();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -104,4 +104,5 @@ flutter:
|
||||||
- assets/animated_icons/
|
- assets/animated_icons/
|
||||||
- assets/animations/
|
- assets/animations/
|
||||||
- assets/passwords/
|
- assets/passwords/
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue