mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:08:41 +00:00
adds optional sentry #299
This commit is contained in:
parent
6d86af155c
commit
091e4bbfa8
18 changed files with 168 additions and 37 deletions
|
|
@ -33,3 +33,4 @@ void Function(SubscriptionPlan plan) globalCallbackUpdatePlan =
|
||||||
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
||||||
|
|
||||||
bool globalIsAppInBackground = true;
|
bool globalIsAppInBackground = true;
|
||||||
|
bool globalAllowErrorTrackingViaSentry = false;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/app.dart';
|
import 'package:twonly/app.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -69,13 +70,34 @@ void main() async {
|
||||||
unawaited(createPushAvatars());
|
unawaited(createPushAvatars());
|
||||||
await twonlyDB.messagesDao.purgeMessageTable();
|
await twonlyDB.messagesDao.purgeMessageTable();
|
||||||
|
|
||||||
|
final providers = [
|
||||||
|
ChangeNotifierProvider(create: (_) => settingsController),
|
||||||
|
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
if (user.allowErrorTrackingViaSentry) {
|
||||||
|
globalAllowErrorTrackingViaSentry = true;
|
||||||
|
return SentryFlutter.init(
|
||||||
|
(options) => options
|
||||||
|
..dsn =
|
||||||
|
'https://6b24a012c85144c9b522440a1d17d01c@glitchtip.twonly.eu/4'
|
||||||
|
..tracesSampleRate = 0.01
|
||||||
|
..enableAutoSessionTracking = false,
|
||||||
|
appRunner: () => runApp(
|
||||||
|
MultiProvider(
|
||||||
|
providers: providers,
|
||||||
|
child: const App(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: providers,
|
||||||
ChangeNotifierProvider(create: (_) => settingsController),
|
|
||||||
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
|
||||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
|
||||||
],
|
|
||||||
child: const App(),
|
child: const App(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
tbl.preKeyId.equals(preKey.preKeyId),
|
tbl.preKeyId.equals(preKey.preKeyId),
|
||||||
))
|
))
|
||||||
.go();
|
.go();
|
||||||
Log.info('[PREKEY] Using prekey ${preKey.preKeyId} for $contactId');
|
|
||||||
return preKey;
|
return preKey;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -68,7 +67,6 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
List<SignalContactPreKeysCompanion> preKeys,
|
List<SignalContactPreKeysCompanion> preKeys,
|
||||||
) async {
|
) async {
|
||||||
for (final preKey in preKeys) {
|
for (final preKey in preKeys) {
|
||||||
Log.info('[PREKEY] Inserting others ${preKey.preKeyId}');
|
|
||||||
try {
|
try {
|
||||||
await into(signalContactPreKeys).insert(preKey);
|
await into(signalContactPreKeys).insert(preKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,12 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
'[PREKEY] No such preKey record! - $preKeyId',
|
'[PREKEY] No such preKey record! - $preKeyId',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Log.info('[PREKEY] Contact used my preKey $preKeyId');
|
|
||||||
final preKey = preKeyRecord.first.preKey;
|
final preKey = preKeyRecord.first.preKey;
|
||||||
return PreKeyRecord.fromBuffer(preKey);
|
return PreKeyRecord.fromBuffer(preKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removePreKey(int preKeyId) async {
|
Future<void> removePreKey(int preKeyId) async {
|
||||||
Log.info('[PREKEY] Removing $preKeyId from my own storage.');
|
|
||||||
await (twonlyDB.delete(twonlyDB.signalPreKeyStores)
|
await (twonlyDB.delete(twonlyDB.signalPreKeyStores)
|
||||||
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
||||||
.go();
|
.go();
|
||||||
|
|
@ -43,7 +41,6 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
preKey: Value(record.serialize()),
|
preKey: Value(record.serialize()),
|
||||||
);
|
);
|
||||||
|
|
||||||
Log.info('[PREKEY] Storing $preKeyId from my own storage.');
|
|
||||||
try {
|
try {
|
||||||
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
|
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -820,5 +820,7 @@
|
||||||
"yourTwonlyScore": "Dein twonly-Score",
|
"yourTwonlyScore": "Dein twonly-Score",
|
||||||
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.",
|
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.",
|
||||||
"dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?",
|
"dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?",
|
||||||
"dialogAskDeleteMediaFilePopDelete": "Löschen"
|
"dialogAskDeleteMediaFilePopDelete": "Löschen",
|
||||||
|
"allowErrorTracking": "Fehler und Crashes mit uns teilen",
|
||||||
|
"allowErrorTrackingSubtitle": "Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen."
|
||||||
}
|
}
|
||||||
|
|
@ -598,5 +598,7 @@
|
||||||
"yourTwonlyScore": "Your twonly-Score",
|
"yourTwonlyScore": "Your twonly-Score",
|
||||||
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.",
|
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.",
|
||||||
"dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?",
|
"dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?",
|
||||||
"dialogAskDeleteMediaFilePopDelete": "Delete"
|
"dialogAskDeleteMediaFilePopDelete": "Delete",
|
||||||
|
"allowErrorTracking": "Share errors and crashes with us",
|
||||||
|
"allowErrorTrackingSubtitle": "If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded."
|
||||||
}
|
}
|
||||||
|
|
@ -2695,6 +2695,18 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Delete'**
|
/// **'Delete'**
|
||||||
String get dialogAskDeleteMediaFilePopDelete;
|
String get dialogAskDeleteMediaFilePopDelete;
|
||||||
|
|
||||||
|
/// No description provided for @allowErrorTracking.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Share errors and crashes with us'**
|
||||||
|
String get allowErrorTracking;
|
||||||
|
|
||||||
|
/// No description provided for @allowErrorTrackingSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.'**
|
||||||
|
String get allowErrorTrackingSubtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1488,4 +1488,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dialogAskDeleteMediaFilePopDelete => 'Löschen';
|
String get dialogAskDeleteMediaFilePopDelete => 'Löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTracking => 'Fehler und Crashes mit uns teilen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTrackingSubtitle =>
|
||||||
|
'Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1478,4 +1478,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dialogAskDeleteMediaFilePopDelete => 'Delete';
|
String get dialogAskDeleteMediaFilePopDelete => 'Delete';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTracking => 'Share errors and crashes with us';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTrackingSubtitle =>
|
||||||
|
'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,6 @@ class UserData {
|
||||||
|
|
||||||
int? defaultShowTime;
|
int? defaultShowTime;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
|
||||||
bool useHighQuality = true;
|
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool requestedAudioPermission = false;
|
bool requestedAudioPermission = false;
|
||||||
|
|
||||||
|
|
@ -73,6 +70,9 @@ class UserData {
|
||||||
|
|
||||||
DateTime? signalLastSignedPreKeyUpdated;
|
DateTime? signalLastSignedPreKeyUpdated;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool allowErrorTrackingViaSentry = false;
|
||||||
|
|
||||||
// -- Custom DATA --
|
// -- Custom DATA --
|
||||||
|
|
||||||
@JsonKey(defaultValue: 100_000)
|
@JsonKey(defaultValue: 100_000)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||||
ThemeMode.system
|
ThemeMode.system
|
||||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||||
..useHighQuality = json['useHighQuality'] as bool? ?? true
|
|
||||||
..requestedAudioPermission =
|
..requestedAudioPermission =
|
||||||
json['requestedAudioPermission'] as bool? ?? false
|
json['requestedAudioPermission'] as bool? ?? false
|
||||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||||
|
|
@ -50,6 +49,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
json['signalLastSignedPreKeyUpdated'] == null
|
json['signalLastSignedPreKeyUpdated'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||||
|
..allowErrorTrackingViaSentry =
|
||||||
|
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||||
..currentPreKeyIndexStart =
|
..currentPreKeyIndexStart =
|
||||||
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||||
..currentSignedPreKeyIndexStart =
|
..currentSignedPreKeyIndexStart =
|
||||||
|
|
@ -84,7 +85,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'todaysImageCounter': instance.todaysImageCounter,
|
'todaysImageCounter': instance.todaysImageCounter,
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'useHighQuality': instance.useHighQuality,
|
|
||||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||||
|
|
@ -96,6 +96,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
||||||
'signalLastSignedPreKeyUpdated':
|
'signalLastSignedPreKeyUpdated':
|
||||||
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
||||||
|
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ Future<void> requestMediaReupload(String mediaId) async {
|
||||||
Future<void> handleEncryptedFile(String mediaId) async {
|
Future<void> handleEncryptedFile(String mediaId) async {
|
||||||
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
||||||
if (mediaService == null) {
|
if (mediaService == null) {
|
||||||
Log.error('Media file $mediaId not found in database.');
|
Log.error('Media file not found in database.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,7 +263,7 @@ Future<void> handleEncryptedFile(String mediaId) async {
|
||||||
try {
|
try {
|
||||||
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Could not read encrypted media file: $mediaId. $e');
|
Log.error('Could not read encrypted media file: $e');
|
||||||
await requestMediaReupload(mediaId);
|
await requestMediaReupload(mediaId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,14 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
Log.error('Receipt $receiptId not found.');
|
Log.error('Receipt not found.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
receiptId = receipt.receiptId;
|
receiptId = receipt.receiptId;
|
||||||
|
|
||||||
if (!onlyReturnEncryptedData && receipt.ackByServerAt != null) {
|
if (!onlyReturnEncryptedData && receipt.ackByServerAt != null) {
|
||||||
Log.error('$receiptId message already uploaded!');
|
Log.error('message already uploaded!');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
|
||||||
void initLogger() {
|
void initLogger() {
|
||||||
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
||||||
|
|
@ -20,6 +22,13 @@ void initLogger() {
|
||||||
|
|
||||||
class Log {
|
class Log {
|
||||||
static void error(Object? message, [Object? error, StackTrace? stackTrace]) {
|
static void error(Object? message, [Object? error, StackTrace? stackTrace]) {
|
||||||
|
if (globalAllowErrorTrackingViaSentry) {
|
||||||
|
try {
|
||||||
|
throw Exception(message);
|
||||||
|
} catch (exception, stackTrace) {
|
||||||
|
Sentry.captureException(exception, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace);
|
Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +54,15 @@ Future<String> loadLogFile() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> readLast1000Lines() async {
|
||||||
|
final dir = await getApplicationSupportDirectory();
|
||||||
|
final file = File('${dir.path}/app.log');
|
||||||
|
if (!file.existsSync()) return '';
|
||||||
|
final all = await file.readAsLines();
|
||||||
|
final start = all.length > 1000 ? all.length - 1000 : 0;
|
||||||
|
return all.sublist(start).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _writeLogToFile(LogRecord record) async {
|
Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Diagnostics')),
|
appBar: AppBar(title: const Text('Diagnostics')),
|
||||||
body: FutureBuilder<String>(
|
body: FutureBuilder<String>(
|
||||||
future: loadLogFile(),
|
future: readLast1000Lines(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
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';
|
||||||
|
|
@ -11,8 +12,22 @@ import 'package:twonly/src/views/settings/help/diagnostics.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/faq.view.dart';
|
import 'package:twonly/src/views/settings/help/faq.view.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class HelpView extends StatelessWidget {
|
class HelpView extends StatefulWidget {
|
||||||
const HelpView({super.key});
|
const HelpView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HelpView> createState() => _HelpViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HelpViewState extends State<HelpView> {
|
||||||
|
Future<void> toggleAllowErrorTrackingViaSentry() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -49,7 +64,10 @@ class HelpView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsResetTutorials),
|
title: Text(context.lang.settingsResetTutorials),
|
||||||
subtitle: Text(context.lang.settingsResetTutorialsDesc),
|
subtitle: Text(
|
||||||
|
context.lang.settingsResetTutorialsDesc,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.tutorialDisplayed = [];
|
user.tutorialDisplayed = [];
|
||||||
|
|
@ -65,6 +83,32 @@ class HelpView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.allowErrorTracking),
|
||||||
|
subtitle: Text(
|
||||||
|
context.lang.allowErrorTrackingSubtitle,
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
onTap: toggleAllowErrorTrackingViaSentry,
|
||||||
|
trailing: Switch(
|
||||||
|
value: gUser.allowErrorTrackingViaSentry,
|
||||||
|
onChanged: (a) => toggleAllowErrorTrackingViaSentry(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.settingsHelpDiagnostics),
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const DiagnosticsView();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: PackageInfo.fromPlatform(),
|
future: PackageInfo.fromPlatform(),
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
|
|
@ -97,19 +141,6 @@ class HelpView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text(context.lang.settingsHelpDiagnostics),
|
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return const DiagnosticsView();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Changelog'),
|
title: const Text('Changelog'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
||||||
32
pubspec.lock
32
pubspec.lock
|
|
@ -976,6 +976,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
jni:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: jni
|
||||||
|
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.2"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1160,6 +1168,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
|
objective_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: objective_c
|
||||||
|
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1440,6 +1456,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.8"
|
version: "0.3.8"
|
||||||
|
sentry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sentry
|
||||||
|
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.8.0"
|
||||||
|
sentry_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sentry_flutter
|
||||||
|
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.8.0"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ dependencies:
|
||||||
restart_app: ^1.3.2
|
restart_app: ^1.3.2
|
||||||
screenshot: ^3.0.0
|
screenshot: ^3.0.0
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
|
sentry_flutter: ^9.8.0
|
||||||
share_plus: ^12.0.0
|
share_plus: ^12.0.0
|
||||||
tutorial_coach_mark: ^1.3.0
|
tutorial_coach_mark: ^1.3.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue