diff --git a/android/app/build.gradle b/android/app/build.gradle index 38fdc7c2..9f7f78c7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -38,7 +38,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "io.sentry.sentry_mobile" + applicationId "io.sentrymobile.app" minSdkVersion 18 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index c4505cdc..1f834e2e 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="io.sentrymobile.app"> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c56dbbc8..fd4f26d5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,16 @@ + package="io.sentrymobile.app"> + + + + if (call.method == "crashKotlin") { + crashingMethod() + } else if (call.method == "crashJava") { + NativeCrashJava.crashingFunction() + } + result.success(0) + } + } + + private fun crashingMethod() { + AsyncTask.execute { + throw Exception("Native Crash - Android Kotlin") + } + } +} diff --git a/android/app/src/main/kotlin/io/sentrymobile/app/NativeCrashJava.java b/android/app/src/main/kotlin/io/sentrymobile/app/NativeCrashJava.java new file mode 100644 index 00000000..193587b7 --- /dev/null +++ b/android/app/src/main/kotlin/io/sentrymobile/app/NativeCrashJava.java @@ -0,0 +1,11 @@ +package io.sentrymobile.app; +import android.os.AsyncTask; + +public class NativeCrashJava { + public static void crashingFunction() { + AsyncTask.execute(() -> { + String test = null; + test.length(); + }); + } +} \ No newline at end of file diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index c4505cdc..1f834e2e 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="io.sentrymobile.app"> diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7b4a8939..eaf9287e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,8 +4,16 @@ PODS: - Flutter - flutter_webview_plugin (0.0.1): - Flutter + - package_info (0.0.1): + - Flutter - path_provider (0.0.1): - Flutter + - Sentry (6.0.9): + - Sentry/Core (= 6.0.9) + - Sentry/Core (6.0.9) + - sentry_flutter (0.0.1): + - Flutter + - Sentry (~> 6.0.9) - shared_preferences (0.0.1): - Flutter - url_launcher (0.0.1): @@ -19,12 +27,18 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) + - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) + - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - webview_cookie_manager (from `.symlinks/plugins/webview_cookie_manager/ios`) - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) +SPEC REPOS: + trunk: + - Sentry + EXTERNAL SOURCES: Flutter: :path: Flutter @@ -32,8 +46,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" flutter_webview_plugin: :path: ".symlinks/plugins/flutter_webview_plugin/ios" + package_info: + :path: ".symlinks/plugins/package_info/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" + sentry_flutter: + :path: ".symlinks/plugins/sentry_flutter/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" url_launcher: @@ -47,7 +65,10 @@ SPEC CHECKSUMS: Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 + package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + Sentry: 388c9dc093b2fd3a264466a5c5b21e25959610a9 + sentry_flutter: b304a61350b0cc38a1417fced3eab99f22f2c277 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 35585b52..9055ae2f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -11,6 +11,7 @@ 1565BABC3730D0B6C35DBDCF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5E58C12BBC4010CC2513402 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 92BDD6D625698B6000937A35 /* NativeCrashObjectiveC.m in Sources */ = {isa = PBXBuildFile; fileRef = 92BDD6D525698B6000937A35 /* NativeCrashObjectiveC.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -38,6 +39,8 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 92BDD6D525698B6000937A35 /* NativeCrashObjectiveC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NativeCrashObjectiveC.m; sourceTree = ""; }; + 92BDD6D725698CB700937A35 /* NativeCrashObjectiveC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NativeCrashObjectiveC.h; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -101,6 +104,8 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 92BDD6D725698CB700937A35 /* NativeCrashObjectiveC.h */, + 92BDD6D525698B6000937A35 /* NativeCrashObjectiveC.m */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; @@ -273,6 +278,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 92BDD6D625698B6000937A35 /* NativeCrashObjectiveC.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -355,6 +361,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 97JCY7859U; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -492,6 +499,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 97JCY7859U; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -523,6 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 97JCY7859U; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4a..a8d768e9 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,11 +3,33 @@ import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + + if let controller = window?.rootViewController as? FlutterViewController { + let batteryChannel = FlutterMethodChannel( + name: "app.sentrymobile.io/nativeCrash", + binaryMessenger: controller.binaryMessenger + ) + batteryChannel.setMethodCallHandler { + [weak self] call, result in + if call.method == "crashSwift" { + self?.crashSwift() + } else if call.method == "crashObjectiveC" { + NativeCrashObjectiveC.crashingFunction() + } + } + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + private func crashSwift() { + let string: String? = nil + print(string!.size) // Force-unwrapping a nil optional crashes the app. + } } diff --git a/ios/Runner/NativeCrashObjectiveC.h b/ios/Runner/NativeCrashObjectiveC.h new file mode 100644 index 00000000..0ed74f30 --- /dev/null +++ b/ios/Runner/NativeCrashObjectiveC.h @@ -0,0 +1,17 @@ +// +// NativeCrashObjectiveC.h +// Runner +// +// Created by Denis Andrašec on 21.11.20. +// + +#ifndef NativeCrashObjectiveC_h +#define NativeCrashObjectiveC_h + +@interface NativeCrashObjectiveC : NSObject + ++ (void)crashingFunction; + +@end + +#endif /* NativeCrashObjectiveC_h */ diff --git a/ios/Runner/NativeCrashObjectiveC.m b/ios/Runner/NativeCrashObjectiveC.m new file mode 100644 index 00000000..ece5a0fb --- /dev/null +++ b/ios/Runner/NativeCrashObjectiveC.m @@ -0,0 +1,23 @@ +// +// NativeCrashObjectiveC.m +// Runner +// +// Created by Denis Andrašec on 21.11.20. +// + +#import + +@interface NativeCrashObjectiveC : NSObject + ++ (void)crashingFunction; + +@end + +@implementation NativeCrashObjectiveC + ++ (void)crashingFunction { + int* p = (int*)1; + *p = 0; // EXC_BAD_ACCESS +} + +@end diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 308a2a56..186b2c8b 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1,2 @@ #import "GeneratedPluginRegistrant.h" +#import "NativeCrashObjectiveC.h" diff --git a/lib/main.dart b/lib/main.dart index bd5047e0..2c5c5b05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'package:sentry_mobile/redux/state/app_state.dart'; import 'package:sentry_mobile/screens/login/login_screen.dart'; import 'package:sentry_mobile/screens/main/main_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; Future> createStore() async { final prefs = await SharedPreferences.getInstance(); @@ -29,7 +30,7 @@ Future> createStore() async { ); } -void main() async { +Future main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations([ @@ -41,13 +42,21 @@ void main() async { store.dispatch(RehydrateAction()); - runApp(SentryMobile(store: store)); + await SentryFlutter.init( + (options) { + options.dsn = 'https://cb0fad6f5d4e42ebb9c956cb0463edc9@o447951.ingest.sentry.io/5428562'; + }, + () { + runApp(StoreProvider( + store: store, + child: SentryMobile(), + )); + } + ); } class SentryMobile extends StatelessWidget { - SentryMobile({this.store}); - - final Store store; + SentryMobile(); @override Widget build(BuildContext context) { @@ -113,18 +122,15 @@ class SentryMobile extends StatelessWidget { color: Colors.black45, )), )), - home: StoreProvider( - store: store, - child: StoreConnector( - builder: (_, state) { - if (state.globalState.session == null) { - return LoginScreen(); - } else { - return MainScreen(); - } - }, - converter: (store) => store.state, - ), + home: StoreConnector( + builder: (_, state) { + if (state.globalState.session == null) { + return LoginScreen(); + } else { + return MainScreen(); + } + }, + converter: (store) => store.state, ) ); } diff --git a/lib/screens/debug/sentry_flutter_screen.dart b/lib/screens/debug/sentry_flutter_screen.dart new file mode 100644 index 00000000..a9ef5304 --- /dev/null +++ b/lib/screens/debug/sentry_flutter_screen.dart @@ -0,0 +1,232 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +class SentryFlutterScreen extends StatefulWidget { + @override + _SentryFlutterScreenState createState() => _SentryFlutterScreenState(); +} + +class _SentryFlutterScreenState extends State { + static const platform = MethodChannel('app.sentrymobile.io/nativeCrash'); + bool _loading = false; + + final _successResultsHandled = { + _TypeToThrow.EXCEPTION: false, + _TypeToThrow.ERROR: false, + _TypeToThrow.STRING: false, + }; + + final _successResultsUnhandled = { + _TypeToThrow.EXCEPTION: false, + _TypeToThrow.ERROR: false, + _TypeToThrow.STRING: false, + }; + + final Map<_TypeToThrow, String> _failureResultsHandled = { + _TypeToThrow.EXCEPTION: null, + _TypeToThrow.ERROR: null, + _TypeToThrow.STRING: null, + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Sentry Flutter SDK - Debug'), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), + child: Text("Captured", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + ), + _createListTile('Sentry.captureException', _TypeToThrow.EXCEPTION), + _createListTile('Sentry.captureException', _TypeToThrow.ERROR), + _createListTile('Sentry.captureException', _TypeToThrow.STRING), + Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), + child: Text("Not Captured", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + ), + _createListTile('Throw Exception', _TypeToThrow.EXCEPTION, fatal: true), + _createListTile('Throw Exception', _TypeToThrow.ERROR, fatal: true), + _createListTile('Throw Exception', _TypeToThrow.STRING, fatal: true), + Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), + child: Text("Native", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + ), + _createNativeListTile(_NativePlatform.Android, true), + _createNativeListTile(_NativePlatform.Android, false), + _createNativeListTile(_NativePlatform.iOS, true), + _createNativeListTile(_NativePlatform.iOS, false) + ], + ), + ) + ); + } + + Widget _createListTile(String title, _TypeToThrow typeToThrow, {bool fatal = false}) { + var subtitle = ''; + if (!fatal) { + subtitle = _successResultsHandled[typeToThrow] + ? 'Success' + : _failureResultsHandled[typeToThrow] != null + ? 'Failure: ${_failureResultsHandled[typeToThrow]}' + : '--'; + } else { + subtitle = _successResultsUnhandled[typeToThrow] + ? 'Success' + : '--'; + } + + return ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle(color: Colors.black) + ), + Text( + '${typeToThrow.name()} Instance', + style: TextStyle(color: Colors.deepPurpleAccent) + ), + ], + ), + subtitle: Text( + subtitle + ), + trailing: RaisedButton( + child: _loading ? Text('Loading...') : fatal ? Text('Throw') : Text('Send'), + onPressed: _loading ? null : () => _captureException(typeToThrow, fatal), + ), + ); + } + + Widget _createNativeListTile(_NativePlatform nativePlatform, bool primaryLanguage) { + return ListTile( + title: Text( + nativePlatform == _NativePlatform.Android ? 'Android' : 'iOS', + style: TextStyle(color: Colors.black) + ), + subtitle: Text( + nativePlatform == _NativePlatform.Android + ? primaryLanguage ? 'Kotlin' : 'Java' + : primaryLanguage ? 'Swift' : 'Objective C', + style: TextStyle(color: Colors.deepPurpleAccent) + ), + trailing: RaisedButton( + child: Text('Crash!'), + onPressed: () => _callCrashNative(nativePlatform, primaryLanguage), + ), + ); + } + + void _captureException(_TypeToThrow typeToThrow, bool fatal) { + setState(() { + _loading = !fatal; + }); + + final title = fatal ? 'Unhandled Exception' : 'Sentry.captureException'; + + dynamic exceptionObject; + + switch(typeToThrow) { + case _TypeToThrow.EXCEPTION: + exceptionObject = Exception('$title - ${typeToThrow.name()} Instance - operatingSystem: ${Platform.operatingSystem}'); + break; + case _TypeToThrow.ERROR: + exceptionObject = _SampleError('$title - ${typeToThrow.name()} Instance - operatingSystem: ${Platform.operatingSystem}'); + break; + case _TypeToThrow.STRING: + exceptionObject = '$title - ${typeToThrow.name()} Instance - operatingSystem: ${Platform.operatingSystem}'; + break; + } + + if (fatal) { + setState(() { + _successResultsUnhandled[typeToThrow] = true; + }); + throw exceptionObject; + } else { + try { + throw exceptionObject; + } catch (exception, stackTrace) { + Sentry.captureException(exception, stackTrace: stackTrace) + .then((value) => { + setState(() { + _loading = false; + _successResultsHandled[typeToThrow] = true; + }) + }) + .catchError((error) => { + setState(() { + _loading = false; + _failureResultsHandled[typeToThrow] = Error.safeToString(error); + }) + }); + } + } + } + + Future _callCrashNative(_NativePlatform nativePlatform, bool primaryLanguage) async { + switch(nativePlatform) { + case _NativePlatform.iOS: + if (primaryLanguage) { + await platform.invokeMethod('crashSwift'); + } else { + await platform.invokeMethod('crashObjectiveC'); + } + break; + case _NativePlatform.Android: + if (primaryLanguage) { + await platform.invokeMethod('crashKotlin'); + } else { + await platform.invokeMethod('crashJava'); + } + break; + } + } +} + +enum _TypeToThrow { + EXCEPTION, ERROR, STRING +} +enum _NativePlatform { + iOS, Android +} + +extension _TypeToThrowPrint on _TypeToThrow { + String name() { + switch(this) { + case _TypeToThrow.EXCEPTION: + return 'Exception'; + break; + case _TypeToThrow.ERROR: + return 'Error'; + break; + case _TypeToThrow.STRING: + return 'String'; + break; + } + } +} + +class _SampleError extends Error { + _SampleError(this.message); + + final Object message; + + @override + String toString() { + return message != null + ? 'Sample error: ${Error.safeToString(message)}' + : 'Unknown error'; + } +} + diff --git a/lib/settings.dart b/lib/settings.dart index 1375586b..6ad2c902 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -9,6 +9,7 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:redux/redux.dart'; import 'package:sentry_mobile/redux/actions.dart'; import 'package:sentry_mobile/redux/state/app_state.dart'; +import 'package:sentry_mobile/screens/debug/sentry_flutter_screen.dart'; import 'package:sentry_mobile/types/organization.dart'; import 'package:sentry_mobile/types/project.dart'; import 'package:webview_cookie_manager/webview_cookie_manager.dart'; @@ -30,10 +31,12 @@ class _SettingsState extends State { return Container( child: Column( children: [ - Text( - 'You are already logged in - Expires: ${viewModel.session.expires}'), + GestureDetector( + onLongPress: _presentSentryFlutterScreen, + child: Text('You are already logged in - Expires: ${viewModel.session.expires}') + ), Center( - child: DropdownButton( + child: DropdownButton( value: viewModel.selectedOrganization?.id, icon: Icon(Icons.arrow_downward), iconSize: 24, @@ -100,6 +103,13 @@ class _SettingsState extends State { ); } + void _presentSentryFlutterScreen() { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => SentryFlutterScreen()), + ); + } + @override Widget build(BuildContext context) { return StoreConnector( diff --git a/pubspec.lock b/pubspec.lock index 49f355e3..17750da1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -387,6 +387,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" + package_info: + dependency: transitive + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3+2" path: dependency: transitive description: @@ -499,6 +506,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + sentry: + dependency: transitive + description: + name: sentry + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0-alpha.2" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0-alpha.2" shared_preferences: dependency: "direct main" description: @@ -665,6 +686,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d0a28534..38bb3907 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: flutter_echarts: ^1.3.5 url_launcher: ^5.5.0 custom_splash: ^0.0.2 + sentry_flutter: ^4.0.0-alpha.2 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.