diff --git a/lib/main.dart b/lib/main.dart index 0073576..8b71160 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:vikunja_app/api/task_implementation.dart'; import 'package:vikunja_app/api/client.dart'; import 'package:vikunja_app/service/services.dart'; @@ -126,24 +127,21 @@ void main() async { )); } -final ValueNotifier updateTheme = ValueNotifier(false); +class ThemeModel with ChangeNotifier { + FlutterThemeMode _themeMode = FlutterThemeMode.light; + FlutterThemeMode get themeMode => _themeMode; -class VikunjaApp extends StatelessWidget { - final Widget home; - final GlobalKey? navkey; + void set themeMode(FlutterThemeMode mode) { + _themeMode = mode; + notifyListeners(); + } - const VikunjaApp({Key? key, required this.home, this.navkey}) - : super(key: key); + void notify() { + notifyListeners(); + } - Future getThemedata() async { - FlutterThemeMode themeMode = FlutterThemeMode.light; - try { - SettingsManager manager = SettingsManager(new FlutterSecureStorage()); - themeMode = await manager.getThemeMode(); - } catch (e) { - print("Failed to get theme mode: $e"); - } - switch (themeMode) { + ThemeData get themeData { + switch (_themeMode) { case FlutterThemeMode.dark: return buildVikunjaDarkTheme(); case FlutterThemeMode.materialYouLight: @@ -155,30 +153,94 @@ class VikunjaApp extends StatelessWidget { } } + ThemeData getWithColorScheme( + ColorScheme? lightTheme, ColorScheme? darkTheme) { + switch (_themeMode) { + case FlutterThemeMode.dark: + return buildVikunjaDarkTheme().copyWith(colorScheme: darkTheme); + case FlutterThemeMode.materialYouLight: + return buildVikunjaMaterialLightTheme() + .copyWith(colorScheme: lightTheme); + case FlutterThemeMode.materialYouDark: + return buildVikunjaMaterialDarkTheme().copyWith(colorScheme: darkTheme); + default: + return buildVikunjaTheme().copyWith(colorScheme: lightTheme); + } + } +} + +ThemeModel themeModel = ThemeModel(); + +class VikunjaApp extends StatelessWidget { + final Widget home; + final GlobalKey? navkey; + bool sentryEnabled = false; + bool sentyInitialized = false; + + VikunjaApp({Key? key, required this.home, this.navkey}) : super(key: key); + + Future getLaunchData() async { + try { + SettingsManager manager = SettingsManager(new FlutterSecureStorage()); + await manager.getThemeMode().then((themeMode) { + themeModel.themeMode = themeMode; + }); + sentryEnabled = await manager.getSentryEnabled(); + } catch (e) { + print("Failed to get theme mode: $e"); + return Future.value(false); + } + return Future.value(true); + } + @override Widget build(BuildContext context) { - return new ValueListenableBuilder( - valueListenable: updateTheme, - builder: (_, mode, __) { - return FutureBuilder( - future: getThemedata(), - builder: (BuildContext context, AsyncSnapshot data) { + return new ListenableBuilder( + listenable: themeModel, + builder: (_, mode) { + return FutureBuilder( + future: getLaunchData(), + builder: (BuildContext context, data) { if (data.hasData) { return new DynamicColorBuilder( builder: (lightTheme, darkTheme) { - ThemeData? themeData = data.data; - if (data.data == FlutterThemeMode.materialYouLight) - themeData = themeData?.copyWith(colorScheme: lightTheme); - else if (data.data == FlutterThemeMode.materialYouDark) - themeData = themeData?.copyWith(colorScheme: darkTheme); - return MaterialApp( - title: 'Vikunja', - theme: themeData, - scaffoldMessengerKey: globalSnackbarKey, - navigatorKey: navkey, - // <= this - home: this.home, - ); + if (sentryEnabled) { + if (!sentyInitialized) { + sentyInitialized = true; + print("sentry enabled"); + SentryFlutter.init((options) { + options.dsn = + 'https://a09618e3bb30e03b93233c21973df869@o1047380.ingest.us.sentry.io/4507995557134336'; + options.tracesSampleRate = 1.0; + options.profilesSampleRate = 1.0; + }).then((_) { + FlutterError.onError = (details) async { + print("sending to sentry"); + await Sentry.captureException( + details.exception, + stackTrace: details.stack, + ); + FlutterError.presentError(details); + }; + PlatformDispatcher.instance.onError = (error, stack) { + print("sending to sentry (platform)"); + Sentry.captureException(error, stackTrace: stack); + FlutterError.presentError(FlutterErrorDetails( + exception: error, stack: stack)); + return false; + }; + }); + } + + return SentryWidget( + child: buildMaterialApp(themeModel.getWithColorScheme( + lightTheme, darkTheme))); + } else { + sentyInitialized = false; + } + + return buildMaterialApp( + themeModel.getWithColorScheme(lightTheme, darkTheme)); }); } else { return Center(child: CircularProgressIndicator()); @@ -186,4 +248,15 @@ class VikunjaApp extends StatelessWidget { }); }); } + + Widget buildMaterialApp(ThemeData? themeData) { + return MaterialApp( + title: 'Vikunja', + theme: themeData, + scaffoldMessengerKey: globalSnackbarKey, + navigatorKey: navkey, + // <= this + home: this.home, + ); + } } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 12d0e28..5692a11 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -17,6 +17,7 @@ class SettingsPageState extends State { List? projectList; int? defaultProject; bool? ignoreCertificates; + bool? sentryEnabled; bool? getVersionNotifications; String? versionTag, newestVersionTag; late TextEditingController durationTextController; @@ -32,14 +33,15 @@ class SettingsPageState extends State { .getAll() .then((value) => setState(() => projectList = value)); - //VikunjaGlobal.of(context).projectService.getDefaultList().then((value) => - // setState( - // () => defaultProject = value == null ? null : int.tryParse(value))); - VikunjaGlobal.of(context).settingsManager.getIgnoreCertificates().then( (value) => setState(() => ignoreCertificates = value == "1" ? true : false)); + VikunjaGlobal.of(context) + .settingsManager + .getSentryEnabled() + .then((value) => setState(() => sentryEnabled = value)); + VikunjaGlobal.of(context).settingsManager.getVersionNotifications().then( (value) => setState( () => getVersionNotifications = value == "1" ? true : false)); @@ -71,7 +73,6 @@ class SettingsPageState extends State { @override Widget build(BuildContext context) { final global = VikunjaGlobal.of(context); - if (!initialized) init(); return new Scaffold( appBar: AppBar( @@ -160,7 +161,7 @@ class SettingsPageState extends State { onChanged: (FlutterThemeMode? value) { VikunjaGlobal.of(context).settingsManager.setThemeMode(value!); setState(() => themeMode = value); - updateTheme.value = true; + if (themeMode != null) themeModel.themeMode = themeMode!; }, ), ), @@ -175,6 +176,22 @@ class SettingsPageState extends State { }) : ListTile(title: Text("...")), Divider(), + sentryEnabled != null + ? CheckboxListTile( + title: Text("Enable Sentry"), + subtitle: Text( + "Help us debug errors better and faster by sending bug reports to us directly. This is completely anonymous."), + value: sentryEnabled, + onChanged: (value) { + if (value == null) return; + setState(() => sentryEnabled = value); + VikunjaGlobal.of(context) + .settingsManager + .setSentryEnabled(value) + .then((_) => themeModel.notify()); + }) + : ListTile(title: Text("...")), + Divider(), Padding( padding: EdgeInsets.only(left: 15, right: 15), child: Row(children: [ diff --git a/lib/service/services.dart b/lib/service/services.dart index c605093..3c69990 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -243,7 +243,8 @@ class SettingsManager { "workmanager-duration": "0", "recent-servers": "[\"https://try.vikunja.io\"]", "theme_mode": "system", - "landing-page-due-date-tasks": "1" + "landing-page-due-date-tasks": "1", + "sentry-enabled": "0", }; void applydefaults() { @@ -268,6 +269,14 @@ class SettingsManager { _storage.write(key: "ignore-certificates", value: value ? "1" : "0"); } + Future getSentryEnabled() { + return _storage.read(key: "sentry-enabled").then((value) => value == "1"); + } + + Future setSentryEnabled(bool value) { + return _storage.write(key: "sentry-enabled", value: value ? "1" : "0"); + } + Future getLandingPageOnlyDueDateTasks() { return _storage .read(key: "landing-page-due-date-tasks") diff --git a/pubspec.lock b/pubspec.lock index c6d4669..5480430 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1141,6 +1141,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + sentry: + dependency: transitive + description: + name: sentry + sha256: "033287044a6644a93498969449d57c37907e56f5cedb17b88a3ff20a882261dd" + url: "https://pub.dev" + source: hosted + version: "8.9.0" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: "3780b5a0bb6afd476857cfbc6c7444d969c29a4d9bd1aa5b6960aa76c65b737a" + url: "https://pub.dev" + source: hosted + version: "8.9.0" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 16f8c2b..565371b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: cronet_http: ^1.2.0 package_info_plus: ^4.2.0 html_editor_enhanced: ^2.6.0 + sentry_flutter: ^8.9.0 dev_dependencies: flutter_test: