From 567f5070e98225e2f84c07d45de84873da12173a Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 18 May 2025 22:19:10 +0200 Subject: [PATCH] Allow sending errors to Sentry --- lib/main.dart | 12 +++- lib/ui/pages/settings.dart | 69 ++++++++++++++++++++ lib/ui/state/settings.dart | 3 + lib/ui/state/settings.freezed.dart | 43 +++++++++--- lib/ui/state/settings.g.dart | 2 + linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + pubspec.lock | 56 ++++++++++++++++ pubspec.yaml | 3 + 9 files changed, 182 insertions(+), 11 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ff0130d..870e3c7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:okane/ui/pages/budgets/budget_details.dart'; import 'package:okane/ui/pages/transaction_details.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/state/settings.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'ui/pages/loans/loan_details.dart'; @@ -21,11 +22,20 @@ Future main() async { final settings = SettingsCubit(); await settings.loadSettings(); + GetIt.I.registerSingleton(settings); GetIt.I.registerSingleton(CoreCubit()); GetIt.I.registerSingleton(OkaneDatabase()); - runApp(const MyApp()); + final appRunner = () => runApp(const MyApp()); + if (settings.sentryDsn != null) { + print("Setting up Sentry!"); + await SentryFlutter.init((options) { + options.dsn = settings.sentryDsn!; + }, appRunner: appRunner); + } else { + appRunner(); + } } class MyApp extends StatelessWidget { diff --git a/lib/ui/pages/settings.dart b/lib/ui/pages/settings.dart index b0f2a1f..d4f0321 100644 --- a/lib/ui/pages/settings.dart +++ b/lib/ui/pages/settings.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/i18n/strings.g.dart'; +import 'package:okane/ui/pages/account/breakdown_card.dart'; import 'package:okane/ui/state/settings.dart'; import 'package:okane/ui/utils.dart'; @@ -67,6 +68,74 @@ class SettingsPage extends StatelessWidget { }, ), ), + + BlocBuilder( + builder: + (context, state) => ListTile( + title: Text("Sentry"), + subtitle: Text( + "When enabled sends errors to the configured Sentry DSN.", + ), + trailing: IconButton( + icon: Icon(Icons.clear), + onPressed: + state.settings.sentryDsn == null + ? null + : () async { + final result = await confirm( + context, + "Clear Sentry DSN", + "Are you sure you want to clear the Sentry DSN?", + ); + if (!result) { + return; + } + + await GetIt.I.get().setSettings( + state.settings.copyWith(sentryDsn: null), + ); + }, + ), + onTap: () async { + final controller = TextEditingController( + text: state.settings.sentryDsn ?? '', + ); + final result = await showDialogOrModal( + context: context, + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: controller, + decoration: InputDecoration(hintText: "Sentry DSN"), + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + child: Text("Apply"), + onPressed: () { + Navigator.of(context).pop(controller.text); + }, + ), + ], + ), + ], + ); + }, + ); + if (result == null) { + return; + } + + await GetIt.I.get().setSettings( + state.settings.copyWith(sentryDsn: result), + ); + }, + ), + ), ], ); } diff --git a/lib/ui/state/settings.dart b/lib/ui/state/settings.dart index 538541e..fbcac98 100644 --- a/lib/ui/state/settings.dart +++ b/lib/ui/state/settings.dart @@ -13,6 +13,7 @@ enum ColorSchemeSettings { light, dark, system } abstract class Settings with _$Settings { const factory Settings({ @Default(ColorSchemeSettings.system) ColorSchemeSettings colorScheme, + @Default(null) String? sentryDsn, }) = _Settings; factory Settings.fromJson(Map json) => @@ -45,4 +46,6 @@ class SettingsCubit extends Cubit { await _prefs.setString("settings", jsonEncode(settings.toJson())); } + + String? get sentryDsn => state.settings.sentryDsn; } diff --git a/lib/ui/state/settings.freezed.dart b/lib/ui/state/settings.freezed.dart index f5cf9a2..8ca248a 100644 --- a/lib/ui/state/settings.freezed.dart +++ b/lib/ui/state/settings.freezed.dart @@ -22,6 +22,7 @@ Settings _$SettingsFromJson(Map json) { /// @nodoc mixin _$Settings { ColorSchemeSettings get colorScheme => throw _privateConstructorUsedError; + String? get sentryDsn => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -34,7 +35,7 @@ abstract class $SettingsCopyWith<$Res> { factory $SettingsCopyWith(Settings value, $Res Function(Settings) then) = _$SettingsCopyWithImpl<$Res, Settings>; @useResult - $Res call({ColorSchemeSettings colorScheme}); + $Res call({ColorSchemeSettings colorScheme, String? sentryDsn}); } /// @nodoc @@ -49,7 +50,7 @@ class _$SettingsCopyWithImpl<$Res, $Val extends Settings> @pragma('vm:prefer-inline') @override - $Res call({Object? colorScheme = null}) { + $Res call({Object? colorScheme = null, Object? sentryDsn = freezed}) { return _then( _value.copyWith( colorScheme: @@ -57,6 +58,11 @@ class _$SettingsCopyWithImpl<$Res, $Val extends Settings> ? _value.colorScheme : colorScheme // ignore: cast_nullable_to_non_nullable as ColorSchemeSettings, + sentryDsn: + freezed == sentryDsn + ? _value.sentryDsn + : sentryDsn // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val, ); @@ -72,7 +78,7 @@ abstract class _$$SettingsImplCopyWith<$Res> ) = __$$SettingsImplCopyWithImpl<$Res>; @override @useResult - $Res call({ColorSchemeSettings colorScheme}); + $Res call({ColorSchemeSettings colorScheme, String? sentryDsn}); } /// @nodoc @@ -86,7 +92,7 @@ class __$$SettingsImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override - $Res call({Object? colorScheme = null}) { + $Res call({Object? colorScheme = null, Object? sentryDsn = freezed}) { return _then( _$SettingsImpl( colorScheme: @@ -94,6 +100,11 @@ class __$$SettingsImplCopyWithImpl<$Res> ? _value.colorScheme : colorScheme // ignore: cast_nullable_to_non_nullable as ColorSchemeSettings, + sentryDsn: + freezed == sentryDsn + ? _value.sentryDsn + : sentryDsn // ignore: cast_nullable_to_non_nullable + as String?, ), ); } @@ -102,7 +113,10 @@ class __$$SettingsImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$SettingsImpl implements _Settings { - const _$SettingsImpl({this.colorScheme = ColorSchemeSettings.system}); + const _$SettingsImpl({ + this.colorScheme = ColorSchemeSettings.system, + this.sentryDsn = null, + }); factory _$SettingsImpl.fromJson(Map json) => _$$SettingsImplFromJson(json); @@ -110,10 +124,13 @@ class _$SettingsImpl implements _Settings { @override @JsonKey() final ColorSchemeSettings colorScheme; + @override + @JsonKey() + final String? sentryDsn; @override String toString() { - return 'Settings(colorScheme: $colorScheme)'; + return 'Settings(colorScheme: $colorScheme, sentryDsn: $sentryDsn)'; } @override @@ -122,12 +139,14 @@ class _$SettingsImpl implements _Settings { (other.runtimeType == runtimeType && other is _$SettingsImpl && (identical(other.colorScheme, colorScheme) || - other.colorScheme == colorScheme)); + other.colorScheme == colorScheme) && + (identical(other.sentryDsn, sentryDsn) || + other.sentryDsn == sentryDsn)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, colorScheme); + int get hashCode => Object.hash(runtimeType, colorScheme, sentryDsn); @JsonKey(ignore: true) @override @@ -142,8 +161,10 @@ class _$SettingsImpl implements _Settings { } abstract class _Settings implements Settings { - const factory _Settings({final ColorSchemeSettings colorScheme}) = - _$SettingsImpl; + const factory _Settings({ + final ColorSchemeSettings colorScheme, + final String? sentryDsn, + }) = _$SettingsImpl; factory _Settings.fromJson(Map json) = _$SettingsImpl.fromJson; @@ -151,6 +172,8 @@ abstract class _Settings implements Settings { @override ColorSchemeSettings get colorScheme; @override + String? get sentryDsn; + @override @JsonKey(ignore: true) _$$SettingsImplCopyWith<_$SettingsImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/ui/state/settings.g.dart b/lib/ui/state/settings.g.dart index 4e07945..6c10b5a 100644 --- a/lib/ui/state/settings.g.dart +++ b/lib/ui/state/settings.g.dart @@ -14,11 +14,13 @@ _$SettingsImpl _$$SettingsImplFromJson(Map json) => json['colorScheme'], ) ?? ColorSchemeSettings.system, + sentryDsn: json['sentryDsn'] as String? ?? null, ); Map _$$SettingsImplToJson(_$SettingsImpl instance) => { 'colorScheme': _$ColorSchemeSettingsEnumMap[instance.colorScheme]!, + 'sentryDsn': instance.sentryDsn, }; const _$ColorSchemeSettingsEnumMap = { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2c1ec4f..a758551 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); + sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7ea2a80..19abbf2 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + sentry_flutter sqlite3_flutter_libs ) diff --git a/pubspec.lock b/pubspec.lock index d751cfe..9dbd556 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -405,6 +405,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -565,6 +573,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: "direct main" description: @@ -685,6 +709,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.9" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" shared_preferences: dependency: "direct main" description: @@ -810,6 +850,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqlite3: dependency: transitive description: @@ -898,6 +946,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3c64c58..ed53377 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,9 @@ dependencies: drift: ^2.26.1 drift_flutter: ^0.2.4 + # For optional error tracking + sentry_flutter: ^8.14.2 + dev_dependencies: flutter_test: sdk: flutter