diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..e4e24035 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: ThexXTURBOXx +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 85b082f2..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG]" -labels: New issue -assignees: jhomlala - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Flutter doctor** -Please add flutter doctor output here. - -**Catcher version** -- Version: - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000..54f10639 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,91 @@ +name: Bug report +description: Create a bug report to help us improve +title: '[Bug]: ' +labels: + - bug +assignees: + - ThexXTURBOXx +body: + - type: textarea + attributes: + label: Description + description: A clear and concise description of the problem + placeholder: Currently, I am trying to [...] + validations: + required: true + - type: textarea + attributes: + label: Minimal Reproduction + description: Provide steps to reproduce the problem + placeholder: 'Steps to reproduce the behaviour: [...]' + value: |- + Steps to reproduce the behaviour: + + 1. Use the following code: + ```dart + + ``` + 2. [...] + validations: + required: true + - type: textarea + attributes: + label: Exception or Error + description: Provide error logs + placeholder: Copy paste from the log/console + validations: + required: true + - type: textarea + attributes: + label: Expected Behaviour + description: A clear and concise description of what you expected to happen + placeholder: The package should [...] + validations: + required: true + - type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem + placeholder: You can upload screenshots by drag’n’drop + - type: textarea + attributes: + label: Additional context + description: Add any other context about the problem here + - type: markdown + attributes: + value: '# Environment' + - type: input + attributes: + label: Device + placeholder: e.g. iPhone 6, Desktop Computer + validations: + required: true + - type: input + attributes: + label: OS + placeholder: e.g. iOS 8.1, Windows 10 21H2 + validations: + required: true + - type: input + attributes: + label: Flutter version + placeholder: e.g. 3.13.9 + validations: + required: true + - type: input + attributes: + label: catcher_2 version + placeholder: e.g. 1.0.0 + validations: + required: true + - type: checkboxes + attributes: + label: Checklist + options: + - label: >- + I have read and followed the **entire** + [README](https://github.com/ThexXTURBOXx/catcher_2/blob/master/README.md) + and it has not provided the solution I need. + required: true + - label: I have provided all the information I can. + required: true diff --git a/.github/ISSUE_TEMPLATE/compatibility-issue.md b/.github/ISSUE_TEMPLATE/compatibility-issue.md new file mode 100644 index 00000000..75631da6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/compatibility-issue.md @@ -0,0 +1,35 @@ +--- +name: Compatibility issue +about: Something is not compatible with the original catcher plugin? +title: '' +labels: compatibility +assignees: ThexXTURBOXx + +--- + +## Description +A clear and concise description of what the issue is. + +## To Reproduce +```dart +Code to reproduce the behavior +``` + +## Expected behavior +A clear and concise description of what you expected to happen. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Device (please complete the following information!) + - Device: [e.g. iPhone 6, Desktop Computer] + - OS: [e.g. iOS 8.1, Windows 10 21H2] + - Browser: [e.g. Stock, Chrome, Safari, Firefox] + - `catcher_2` version: [e.g. 1.0.0] + +## Additional context +Add any other context about the problem here. + +## Checklist + - [ ] I have read and followed the **entire** [README](https://github.com/ThexXTURBOXx/catcher_2) and it has not provided the solution I need. + - [ ] I have provided all the information I can (incl. auth URL etc.) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7cf0bce3..3918ae50 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,20 @@ --- name: Feature request about: Suggest an idea for this project -title: "[FEATURE]" -labels: New issue, enhancement -assignees: jhomlala +title: '' +labels: enhancement +assignees: ThexXTURBOXx --- -**Is your feature request related to a problem? Please describe.** +## Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** +## Describe the solution you'd like A clear and concise description of what you want to happen. -**Describe alternatives you've considered** +## Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. -**Additional context** +## Additional context Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f37cb1b9..2e8e565d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,30 +7,26 @@ on: jobs: check-format: - name: Check format using dart format. + name: Check format using flutter format runs-on: ubuntu-latest + container: ghcr.io/cirruslabs/flutter:stable steps: - name: Checkout code - uses: actions/checkout@v2 - - name: Flutter Action - uses: subosito/flutter-action@v2 + uses: actions/checkout@v4 - name: Check format - run: dart format . --set-exit-if-changed + run: dart format --output=none --set-exit-if-changed . lint: name: Lint runs-on: ubuntu-latest + container: ghcr.io/cirruslabs/flutter:stable steps: - name: Checkout code - uses: actions/checkout@v2 - - name: Flutter Action - uses: subosito/flutter-action@v2 - - name: Install Package Dependencies - run: flutter packages get + uses: actions/checkout@v4 - name: Get dependencies for example run: flutter pub get working-directory: example - name: Lint using flutter analyze - run: flutter analyze . \ No newline at end of file + run: flutter analyze diff --git a/.gitignore b/.gitignore index 9edef099..f2ab653f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,49 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp .DS_Store -.dart_tool/ -.idea +.atom/ +.buildlog/ +.gradle/ +.history +.svn/ +migrate_working_dir/ -.packages -.pub/ +# IntelliJ related +*.iml +*.ipr +*.iws .idea/ -build/ -ios/.generated/ -ios/Flutter/Generated.xcconfig -ios/Runner/GeneratedPluginRegistrant.* -example/macos/Flutter/ -example/linux/flutter/ - -android/.gradle +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock +pubspec_overrides.yaml +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ .flutter-plugins .flutter-plugins-dependencies -flutter_export_environment.sh -*.lock -.vscode +.packages +.pub-cache/ +.pub/ +/build/ +local.properties + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata index 2517d637..c21c4bcc 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,42 @@ # This file should be version controlled and should not be manually edited. version: - revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b - channel: stable + revision: "b0850beeb25f6d5b10426284f506557f66181b36" + channel: "stable" project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: android + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: ios + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: linux + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: macos + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: web + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: windows + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md index 76dfd0b7..cfbaa9b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,109 @@ -## 1.0.0-dev1 -* [BREAKING_CHANGE] Changed format of Catcher from plugin to package. -* [BREAKING_CHANGE] Updated Dart SDK min version to 3.0.0 and Flutter version to 3.3.0. -* Regenerated example project. -* Updated dependencies. - -## 0.8.0 -* [BREAKING_CHANGE] Updated min android version to 22 and target/compile version to 34. -* Updated dependencies version. -* Replaced lint with very_good_analysis package. -* Fixed screenshot manger not creating screenshots. -* Update pub.dev metadata. +## 2.1.0 +* Add `extraData` parameter to `reportCheckedError` ([#40](https://github.com/ThexXTURBOXx/catcher_2/issues/40)) + +## 2.0.2 +* Fix logger after `updateConfig` ([#39](https://github.com/ThexXTURBOXx/catcher_2/issues/39)) + +## 2.0.1 +* Allow `device_info_plus` versions `11.x` +* Fix example on Android (build with JDK 21 failed) + +## 2.0.0 +* Bump to next stable version +* Improve logging ([#37](https://github.com/ThexXTURBOXx/catcher_2/issues/37)) + +## 2.0.0-alpha.2 +* Fix analyser issues for Flutter `3.24.0` +* Update `fluttertoast` requirement to `8.2.6` + +## 2.0.0-alpha.1 +* Apply proper colours from App theme ([#31](https://github.com/ThexXTURBOXx/catcher_2/issues/31)) + +## 2.0.0-alpha.0 +* [BREAKING_CHANGE] Migrate Slack screenshot API calls to `files.*UploadExternal` (you now need to specify also a `channelId` in the `SlackHandler` for that!) +* [BREAKING_CHANGE] Migrate screenshots to `cross_file`'s `XFile`s +* [BREAKING_CHANGE] Migrate away from Android `namespace` workaround (removes support for ancient AGP versions) +* [BREAKING_CHANGE] Fix error catching on Web (was also improved thanks to [@mikeesouth](https://github.com/mikeesouth) in [#32](https://github.com/ThexXTURBOXx/catcher_2/pull/32)) +* Added more parameters to `SentryHandler` (Thanks to [@mikeesouth](https://github.com/mikeesouth) in [#32](https://github.com/ThexXTURBOXx/catcher_2/pull/32)) +* Better example structure +* Update `flutter_lints` to `4.x` +* Rebase on upstream + +## 1.2.6 +* Allow `package_info_plus` versions `8.x` +* Remove direct dependency on `device_info_plus_platform_interface` (why was this there anyway?) + +## 1.2.5 +* Allow `package_info_plus` versions `7.x` + +## 1.2.4 +* Allow `package_info_plus` versions `6.x` (Thanks to [@bartektartanus](https://github.com/bartektartanus) in [#28](https://github.com/ThexXTURBOXx/catcher_2/pull/28)) +* Allow `device_info_plus` versions `10.x` + +## 1.2.3 +* Fix `FileHandler` and optimize it + +## 1.2.2 +* Fix a few typos in the README (Thanks to [@mrclauss](https://github.com/mrclauss) in [#23](https://github.com/ThexXTURBOXx/catcher_2/pull/23)) +* Fix lint for Flutter `3.19.x` + +## 1.2.1 +* Add `fileSupplier` to `FileHandler` ([#21](https://github.com/ThexXTURBOXx/catcher_2/issues/21) and [#22](https://github.com/ThexXTURBOXx/catcher_2/issues/22)) +* Allow `sentry` versions `8.x` + +## 1.2.0 +* Add screenshot support for Sentry (Thanks to [@mikeesouth](https://github.com/mikeesouth) in [#20](https://github.com/ThexXTURBOXx/catcher_2/pull/20)) + +## 1.1.0 +* Add screenshot support for Slack (Thanks to [@ramaarf](https://github.com/ramaarf) in [#18](https://github.com/ThexXTURBOXx/catcher_2/pull/18)) +* Add Turkish translation (Thanks to [@anilaydinn](https://github.com/anilaydinn) in [#266](https://github.com/jhomlala/catcher/pull/266)) + +## 1.0.7 +* Fix stack trace in console handler (Thanks to [@MilovdZee](https://github.com/MilovdZee) in [#16](https://github.com/ThexXTURBOXx/catcher_2/pull/16)) + +## 1.0.6 +* Add Arabic translation ([#265](https://github.com/jhomlala/catcher/pull/265)) +* Improve Russian translation ([#256](https://github.com/jhomlala/catcher/pull/256)) +* Merge upstream `catcher` changes (only smaller things) + +## 1.0.5 +* Allow `package_info_plus` versions `5.x` +* Update to Flutter `3.16` + +## 1.0.4 +* Cleanup in many places (also makes a few things more robust) +* Fix `null` path for screenshot manager ([#12](https://github.com/ThexXTURBOXx/catcher_2/issues/12)) + +## 1.0.3 +* Add even more error resilience to calls to 3rd party libraries + +## 1.0.2 +* Add error resilience to calls to 3rd party libraries +* Improve documentation + +## 1.0.1 +* Fix initialisation order ([#10](https://github.com/ThexXTURBOXx/catcher_2/issues/10)) +* Allow additional, custom error handlers ([#11](https://github.com/ThexXTURBOXx/catcher_2/issues/11)) +* Update `flutter_lints` and clean up implementation + +## 1.0.0 +* Stable version arrived, yay! +* Add `senderUsername` to set an explicit username for SMTP authentication + +## 1.0.0-alpha.1 +* Added migration guide + +## 1.0.0-alpha.0 +* [BREAKING_CHANGE] Rebrand to `catcher_2` +* [BREAKING_CHANGE] Fix compatibility with newer versions of Flutter. Only SDK `>=3.0.0` is supported now +* [BREAKING_CHANGE] Update to `dio` 5.x. This has an effect on the `HttpHandler` +* [BREAKING_CHANGE] Update to `sentry` 7.x +* Fix compatibility with AGP 8.x +* Fix wrong stack trace being sent to Sentry +* Fix a few typos in the README +* Fix many other errors as well +* Update package dependencies +* Update example dependencies ## 0.7.0 * [BREAKING_CHANGE] Update to Flutter 3 by Nico Mexis (https://github.com/ThexXTURBOXx). diff --git a/README.md b/README.md index 13b8e92d..db9ed3e0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@

- +

-# Catcher +# Catcher 2 -[![pub package](https://img.shields.io/pub/v/catcher.svg)](https://pub.dartlang.org/packages/catcher) -[![pub package](https://img.shields.io/github/license/jhomlala/catcher.svg?style=flat)](https://github.com/jhomlala/catcher) -[![pub package](https://img.shields.io/badge/platform-flutter-blue.svg)](https://github.com/jhomlala/catcher) -[![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter) +**This project is a continuation of [catcher](https://github.com/jhomlala/catcher) by Jakub Homlala with many new features and bug fixes.** +[![pub package](https://img.shields.io/pub/v/catcher_2.svg)](https://pub.dartlang.org/packages/catcher_2) +[![CI](https://github.com/ThexXTURBOXx/catcher_2/actions/workflows/ci.yml/badge.svg)](https://github.com/ThexXTURBOXx/catcher_2/actions/workflows/ci.yml) +[![license](https://img.shields.io/github/license/ThexXTURBOXx/catcher_2.svg?style=flat)](https://github.com/ThexXTURBOXx/catcher_2) +[![flutter](https://img.shields.io/badge/platform-flutter-blue.svg)](https://github.com/ThexXTURBOXx/catcher_2) +[![awesome flutter](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter) -Catcher is Flutter plugin which automatically catches error/exceptions and handle them. Catcher offers multiple way to handle errors. -Catcher is heavily inspired from ACRA: https://github.com/ACRA/acra. -Catcher supports Android, iOS, Web, Linux, Windows and MacOS platforms. + +Catcher 2 is a Flutter plugin which automatically catches errors/exceptions and offers multiple ways to handle them.
+It is heavily inspired from ACRA: https://github.com/ACRA/acra.
+It supports Android, iOS, Web, Linux, Windows and MacOS platforms. ## Install @@ -20,7 +23,7 @@ Catcher supports Android, iOS, Web, Linux, Windows and MacOS platforms. Add this line to your **pubspec.yaml**: ```yaml dependencies: - catcher: ^1.0.0-dev1 + catcher_2: ^2.0.0-alpha.0 ``` Then run this command: @@ -30,16 +33,29 @@ $ flutter packages get Then add this import: ```dart -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; ``` +And now you can use all the features as demonstrated below! + +## Upgrading from `catcher` + +If you used `catcher` correctly (and without extra hackage) before, it should be sufficient to replace the following strings *everywhere*: +- `Catcher` -> `Catcher2` +- `catcher` -> `catcher_2` (only in very few places you need to use `catcher2` instead) + +Also note the following: +- `HttpHandler` had some breaking changes due to the upgrade to `dio` 5.x + +If you are still unsure or something is not working as well as before, please [open a new issue](https://github.com/ThexXTURBOXx/catcher_2/issues/new/choose). + ## Table of contents [Platform support](#platform-support) [Basic example](#basic-example) -[Catcher usage](#catcher-usage) +[Catcher 2 usage](#catcher-2-usage) [Adding navigator key](#adding-navigator-key) -[Catcher configuration](#catcher-configuration) -[Report catched exception](#report-catched-exception) +[Catcher 2 configuration](#catcher-2-configuration) +[Report caught exception](#report-caught-exception) [Localization](#localization) [Report modes](#report-modes) @@ -70,35 +86,35 @@ import 'package:catcher/catcher.dart'; [Screenshots](#screenshots) ## Platform support -To check which features of Catcher are available in given platform visit this page: [Platform support](https://github.com/jhomlala/catcher/blob/master/platform_support.md) +To check which features of Catcher 2 are available in given platform visit this page: [Platform support](https://github.com/ThexXTURBOXx/catcher_2/blob/master/platform_support.md) ## Basic example Basic example utilizes debug config with Dialog Report Mode and Console Handler and release config with Dialog Report Mode and Email Manual Handler. -To start using Catcher, you have to: -1. Create Catcher configuration (you can use only debug config at start) -2. Create Catcher instance and pass your root widget along with catcher configuration +To start using Catcher 2, you have to: +1. Create Catcher 2 configuration (you can use only debug config at start) +2. Create Catcher 2 instance and pass your root widget along with its configuration 3. Add navigator key to MaterialApp or CupertinoApp Here is complete example: ```dart import 'package:flutter/material.dart'; -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; main() { - /// STEP 1. Create catcher configuration. + /// STEP 1. Create Catcher 2 configuration. /// Debug configuration with dialog report mode and console handler. It will show dialog and once user accepts it, error will be shown /// in console. - CatcherOptions debugOptions = - CatcherOptions(DialogReportMode(), [ConsoleHandler()]); + Catcher2Options debugOptions = + Catcher2Options(DialogReportMode(), [ConsoleHandler()]); /// Release configuration. Same as above, but once user accepts dialog, user will be prompted to send email with crash to support. - CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [ + Catcher2Options releaseOptions = Catcher2Options(DialogReportMode(), [ EmailManualHandler(["support@email.com"]) ]); - /// STEP 2. Pass your root widget (MyApp) along with Catcher configuration: - Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); + /// STEP 2. Pass your root widget (MyApp) along with Catcher 2 configuration: + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); } class MyApp extends StatefulWidget { @@ -115,8 +131,8 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - /// STEP 3. Add navigator key from Catcher. It will be used to navigate user to report page or to show dialog. - navigatorKey: Catcher.navigatorKey, + /// STEP 3. Add navigator key from Catcher 2. It will be used to navigate user to report page or to show dialog. + navigatorKey: Catcher2.navigatorKey, home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), @@ -142,95 +158,97 @@ class ChildWidget extends StatelessWidget { ``` If you run this code you will see screen with "Generate error" button on the screen. -After clicking on it, it will generate test exception, which will be handled by Catcher. Before Catcher process exception to handler, it will +After clicking on it, it will generate test exception, which will be handled by Catcher 2. Before Catcher 2 process exception to handler, it will show dialog with information for user. This dialog is shown because we have used DialogReportHandler. Once user confirms action in this dialog, -report will be send to console handler which will log to console error informations. +report will be send to console handler which will log to console error information.

-
+
Dialog with default confirmation message

```dart -I/flutter ( 7457): [2019-02-09 12:40:21.527271 | ConsoleHandler | INFO] ============================== CATCHER LOG ============================== -I/flutter ( 7457): [2019-02-09 12:40:21.527742 | ConsoleHandler | INFO] Crash occured on 2019-02-09 12:40:20.424286 -I/flutter ( 7457): [2019-02-09 12:40:21.527827 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.527908 | ConsoleHandler | INFO] ------- DEVICE INFO ------- -I/flutter ( 7457): [2019-02-09 12:40:21.528233 | ConsoleHandler | INFO] id: PSR1.180720.061 -I/flutter ( 7457): [2019-02-09 12:40:21.528337 | ConsoleHandler | INFO] androidId: 726e4abc58dde277 -I/flutter ( 7457): [2019-02-09 12:40:21.528431 | ConsoleHandler | INFO] board: goldfish_x86 -I/flutter ( 7457): [2019-02-09 12:40:21.528512 | ConsoleHandler | INFO] bootloader: unknown -I/flutter ( 7457): [2019-02-09 12:40:21.528595 | ConsoleHandler | INFO] brand: google -I/flutter ( 7457): [2019-02-09 12:40:21.528694 | ConsoleHandler | INFO] device: generic_x86 -I/flutter ( 7457): [2019-02-09 12:40:21.528774 | ConsoleHandler | INFO] display: sdk_gphone_x86-userdebug 9 PSR1.180720.061 5075414 dev-keys -I/flutter ( 7457): [2019-02-09 12:40:21.528855 | ConsoleHandler | INFO] fingerprint: google/sdk_gphone_x86/generic_x86:9/PSR1.180720.061/5075414:userdebug/dev-keys -I/flutter ( 7457): [2019-02-09 12:40:21.528939 | ConsoleHandler | INFO] hardware: ranchu -I/flutter ( 7457): [2019-02-09 12:40:21.529023 | ConsoleHandler | INFO] host: vped9.mtv.corp.google.com -I/flutter ( 7457): [2019-02-09 12:40:21.529813 | ConsoleHandler | INFO] isPsychicalDevice: false -I/flutter ( 7457): [2019-02-09 12:40:21.530178 | ConsoleHandler | INFO] manufacturer: Google -I/flutter ( 7457): [2019-02-09 12:40:21.530345 | ConsoleHandler | INFO] model: Android SDK built for x86 -I/flutter ( 7457): [2019-02-09 12:40:21.530443 | ConsoleHandler | INFO] product: sdk_gphone_x86 -I/flutter ( 7457): [2019-02-09 12:40:21.530610 | ConsoleHandler | INFO] tags: dev-keys -I/flutter ( 7457): [2019-02-09 12:40:21.530713 | ConsoleHandler | INFO] type: userdebug -I/flutter ( 7457): [2019-02-09 12:40:21.530825 | ConsoleHandler | INFO] versionBaseOs: -I/flutter ( 7457): [2019-02-09 12:40:21.530922 | ConsoleHandler | INFO] versionCodename: REL -I/flutter ( 7457): [2019-02-09 12:40:21.531074 | ConsoleHandler | INFO] versionIncremental: 5075414 -I/flutter ( 7457): [2019-02-09 12:40:21.531573 | ConsoleHandler | INFO] versionPreviewSdk: 0 -I/flutter ( 7457): [2019-02-09 12:40:21.531659 | ConsoleHandler | INFO] versionRelase: 9 -I/flutter ( 7457): [2019-02-09 12:40:21.531740 | ConsoleHandler | INFO] versionSdk: 28 -I/flutter ( 7457): [2019-02-09 12:40:21.531870 | ConsoleHandler | INFO] versionSecurityPatch: 2018-08-05 -I/flutter ( 7457): [2019-02-09 12:40:21.532002 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.532078 | ConsoleHandler | INFO] ------- APP INFO ------- -I/flutter ( 7457): [2019-02-09 12:40:21.532167 | ConsoleHandler | INFO] version: 1.0 -I/flutter ( 7457): [2019-02-09 12:40:21.532250 | ConsoleHandler | INFO] appName: catcher_example -I/flutter ( 7457): [2019-02-09 12:40:21.532345 | ConsoleHandler | INFO] buildNumber: 1 -I/flutter ( 7457): [2019-02-09 12:40:21.532426 | ConsoleHandler | INFO] packageName: com.jhomlala.catcherexample -I/flutter ( 7457): [2019-02-09 12:40:21.532667 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.532944 | ConsoleHandler | INFO] ---------- ERROR ---------- -I/flutter ( 7457): [2019-02-09 12:40:21.533096 | ConsoleHandler | INFO] Test exception -I/flutter ( 7457): [2019-02-09 12:40:21.533179 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.533257 | ConsoleHandler | INFO] ------- STACK TRACE ------- -I/flutter ( 7457): [2019-02-09 12:40:21.533695 | ConsoleHandler | INFO] #0 ChildWidget.generateError (package:catcher_example/file_example.dart:62:5) -I/flutter ( 7457): [2019-02-09 12:40:21.533799 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.533879 | ConsoleHandler | INFO] #1 ChildWidget.build. (package:catcher_example/file_example.dart:53:61) -I/flutter ( 7457): [2019-02-09 12:40:21.534149 | ConsoleHandler | INFO] #2 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14) -I/flutter ( 7457): [2019-02-09 12:40:21.534230 | ConsoleHandler | INFO] #3 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:562:30) -I/flutter ( 7457): [2019-02-09 12:40:21.534321 | ConsoleHandler | INFO] #4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24) -I/flutter ( 7457): [2019-02-09 12:40:21.534419 | ConsoleHandler | INFO] #5 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9) -I/flutter ( 7457): [2019-02-09 12:40:21.534524 | ConsoleHandler | INFO] #6 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:175:7) -I/flutter ( 7457): [2019-02-09 12:40:21.534608 | ConsoleHandler | INFO] #7 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9) -I/flutter ( 7457): [2019-02-09 12:40:21.534686 | ConsoleHandler | INFO] #8 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12) -I/flutter ( 7457): [2019-02-09 12:40:21.534765 | ConsoleHandler | INFO] #9 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11) -I/flutter ( 7457): [2019-02-09 12:40:21.534843 | ConsoleHandler | INFO] #10 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:180:19) -I/flutter ( 7457): [2019-02-09 12:40:21.534973 | ConsoleHandler | INFO] #11 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:158:22) -I/flutter ( 7457): [2019-02-09 12:40:21.535052 | ConsoleHandler | INFO] #12 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:138:7) -I/flutter ( 7457): [2019-02-09 12:40:21.535136 | ConsoleHandler | INFO] #13 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:101:7) -I/flutter ( 7457): [2019-02-09 12:40:21.535216 | ConsoleHandler | INFO] #14 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:85:7) -I/flutter ( 7457): [2019-02-09 12:40:21.535600 | ConsoleHandler | INFO] #15 _rootRunUnary (dart:async/zone.dart:1136:13) -I/flutter ( 7457): [2019-02-09 12:40:21.535753 | ConsoleHandler | INFO] #16 _CustomZone.runUnary (dart:async/zone.dart:1029:19) -I/flutter ( 7457): [2019-02-09 12:40:21.536008 | ConsoleHandler | INFO] #17 _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7) -I/flutter ( 7457): [2019-02-09 12:40:21.536138 | ConsoleHandler | INFO] #18 _invoke1 (dart:ui/hooks.dart:170:10) -I/flutter ( 7457): [2019-02-09 12:40:21.536271 | ConsoleHandler | INFO] #19 _dispatchPointerDataPacket (dart:ui/hooks.dart:122:5) -I/flutter ( 7457): [2019-02-09 12:40:21.536375 | ConsoleHandler | INFO] -I/flutter ( 7457): [2019-02-09 12:40:21.536539 | ConsoleHandler | INFO] ====================================================================== +I/flutter ( 1792): [2023-09-26 20:40:59.075029 | Catcher 2 | INFO] ============================== CATCHER 2 LOG ============================== +I/flutter ( 1792): [2023-09-26 20:40:59.075175 | Catcher 2 | INFO] Crash occurred on 2023-09-26 20:40:57.818695 +I/flutter ( 1792): [2023-09-26 20:40:59.075220 | Catcher 2 | INFO] +I/flutter ( 1792): [2023-09-26 20:40:59.075378 | Catcher 2 | INFO] ------- DEVICE INFO ------- +I/flutter ( 1792): [2023-09-26 20:40:59.075755 | Catcher 2 | INFO] id: TQ3A.230705.001.B4 +I/flutter ( 1792): [2023-09-26 20:40:59.075830 | Catcher 2 | INFO] board: windows +I/flutter ( 1792): [2023-09-26 20:40:59.075874 | Catcher 2 | INFO] bootloader: unknown +I/flutter ( 1792): [2023-09-26 20:40:59.075901 | Catcher 2 | INFO] brand: Windows +I/flutter ( 1792): [2023-09-26 20:40:59.075940 | Catcher 2 | INFO] device: windows_x86_64 +I/flutter ( 1792): [2023-09-26 20:40:59.075966 | Catcher 2 | INFO] display: TQ3A.230705.001.B4 +I/flutter ( 1792): [2023-09-26 20:40:59.076003 | Catcher 2 | INFO] fingerprint: Windows/windows_x86_64/windows_x86_64:13/TQ3A.230705.001.B4/2307.40000.6.0:user/release-keys +I/flutter ( 1792): [2023-09-26 20:40:59.076047 | Catcher 2 | INFO] hardware: windows_x86_64 +I/flutter ( 1792): [2023-09-26 20:40:59.076089 | Catcher 2 | INFO] host: bba7b442c000000 +I/flutter ( 1792): [2023-09-26 20:40:59.076169 | Catcher 2 | INFO] isPhysicalDevice: true +I/flutter ( 1792): [2023-09-26 20:40:59.076224 | Catcher 2 | INFO] manufacturer: Microsoft Corporation +I/flutter ( 1792): [2023-09-26 20:40:59.076269 | Catcher 2 | INFO] model: Subsystem for Android(TM) +I/flutter ( 1792): [2023-09-26 20:40:59.076310 | Catcher 2 | INFO] product: windows_x86_64 +I/flutter ( 1792): [2023-09-26 20:40:59.076346 | Catcher 2 | INFO] tags: release-keys +I/flutter ( 1792): [2023-09-26 20:40:59.076371 | Catcher 2 | INFO] type: user +I/flutter ( 1792): [2023-09-26 20:40:59.076404 | Catcher 2 | INFO] versionBaseOs: +I/flutter ( 1792): [2023-09-26 20:40:59.076430 | Catcher 2 | INFO] versionCodename: REL +I/flutter ( 1792): [2023-09-26 20:40:59.076462 | Catcher 2 | INFO] versionIncremental: 2307.40000.6.0 +I/flutter ( 1792): [2023-09-26 20:40:59.076487 | Catcher 2 | INFO] versionPreviewSdk: 0 +I/flutter ( 1792): [2023-09-26 20:40:59.076521 | Catcher 2 | INFO] versionRelease: 13 +I/flutter ( 1792): [2023-09-26 20:40:59.076573 | Catcher 2 | INFO] versionSdk: 33 +I/flutter ( 1792): [2023-09-26 20:40:59.076611 | Catcher 2 | INFO] versionSecurityPatch: 2023-07-05 +I/flutter ( 1792): [2023-09-26 20:40:59.076640 | Catcher 2 | INFO] +I/flutter ( 1792): [2023-09-26 20:40:59.076759 | Catcher 2 | INFO] ------- APP INFO ------- +I/flutter ( 1792): [2023-09-26 20:40:59.076829 | Catcher 2 | INFO] environment: debug +I/flutter ( 1792): [2023-09-26 20:40:59.076867 | Catcher 2 | INFO] version: 1.0.0 +I/flutter ( 1792): [2023-09-26 20:40:59.076903 | Catcher 2 | INFO] appName: catcher_2_example +I/flutter ( 1792): [2023-09-26 20:40:59.076941 | Catcher 2 | INFO] buildNumber: 1 +I/flutter ( 1792): [2023-09-26 20:40:59.076978 | Catcher 2 | INFO] packageName: com.jhomlala.catcher_2_example +I/flutter ( 1792): [2023-09-26 20:40:59.077006 | Catcher 2 | INFO] +I/flutter ( 1792): [2023-09-26 20:40:59.077040 | Catcher 2 | INFO] ---------- ERROR ---------- +I/flutter ( 1792): [2023-09-26 20:40:59.077079 | Catcher 2 | INFO] FormatException: Test exception generated by Catcher 2 +I/flutter ( 1792): [2023-09-26 20:40:59.077125 | Catcher 2 | INFO] +I/flutter ( 1792): [2023-09-26 20:40:59.077267 | Catcher 2 | INFO] ------- STACK TRACE ------- +I/flutter ( 1792): [2023-09-26 20:40:59.077461 | Catcher 2 | INFO] #0 Catcher2.sendTestException (package:catcher_2/core/catcher_2.dart:706:5) +I/flutter ( 1792): [2023-09-26 20:40:59.077523 | Catcher 2 | INFO] #1 ChildWidget.generateError (package:catcher_2_example/main.dart:89:14) +I/flutter ( 1792): [2023-09-26 20:40:59.077562 | Catcher 2 | INFO] #2 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1154:21) +I/flutter ( 1792): [2023-09-26 20:40:59.077588 | Catcher 2 | INFO] #3 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:275:24) +I/flutter ( 1792): [2023-09-26 20:40:59.077611 | Catcher 2 | INFO] #4 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:654:11) +I/flutter ( 1792): [2023-09-26 20:40:59.077643 | Catcher 2 | INFO] #5 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:311:5) +I/flutter ( 1792): [2023-09-26 20:40:59.077690 | Catcher 2 | INFO] #6 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:244:7) +I/flutter ( 1792): [2023-09-26 20:40:59.077719 | Catcher 2 | INFO] #7 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:630:9) +I/flutter ( 1792): [2023-09-26 20:40:59.077761 | Catcher 2 | INFO] #8 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12) +I/flutter ( 1792): [2023-09-26 20:40:59.077811 | Catcher 2 | INFO] #9 PointerRouter._dispatchEventToRoutes. (package:flutter/src/gestures/pointer_router.dart:143:9) +I/flutter ( 1792): [2023-09-26 20:40:59.077869 | Catcher 2 | INFO] #10 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13) +I/flutter ( 1792): [2023-09-26 20:40:59.077908 | Catcher 2 | INFO] #11 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18) +I/flutter ( 1792): [2023-09-26 20:40:59.077945 | Catcher 2 | INFO] #12 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7) +I/flutter ( 1792): [2023-09-26 20:40:59.077969 | Catcher 2 | INFO] #13 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:488:19) +I/flutter ( 1792): [2023-09-26 20:40:59.078002 | Catcher 2 | INFO] #14 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:468:22) +I/flutter ( 1792): [2023-09-26 20:40:59.078036 | Catcher 2 | INFO] #15 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:333:11) +I/flutter ( 1792): [2023-09-26 20:40:59.078077 | Catcher 2 | INFO] #16 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:413:7) +I/flutter ( 1792): [2023-09-26 20:40:59.078123 | Catcher 2 | INFO] #17 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:376:5) +I/flutter ( 1792): [2023-09-26 20:40:59.078174 | Catcher 2 | INFO] #18 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:323:7) +I/flutter ( 1792): [2023-09-26 20:40:59.078209 | Catcher 2 | INFO] #19 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:292:9) +I/flutter ( 1792): [2023-09-26 20:40:59.078250 | Catcher 2 | INFO] #20 _invoke1 (dart:ui/hooks.dart:186:13) +I/flutter ( 1792): [2023-09-26 20:40:59.078286 | Catcher 2 | INFO] #21 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:424:7) +I/flutter ( 1792): [2023-09-26 20:40:59.078310 | Catcher 2 | INFO] #22 _dispatchPointerDataPacket (dart:ui/hooks.dart:119:31) +I/flutter ( 1792): [2023-09-26 20:40:59.078340 | Catcher 2 | INFO] +I/flutter ( 1792): [2023-09-26 20:40:59.078397 | Catcher 2 | INFO] ====================================================================== ```

Console handler output

-## Catcher usage +## Catcher 2 usage ### Adding navigator key -In order to make work Page Report Mode and Dialog Report Mode, you must include navigator key. Catcher plugin exposes key which must be included in your MaterialApp or WidgetApp: +In order to make Page Report Mode and Dialog Report Mode work, you must include navigator key. Catcher 2 plugin exposes the key which must be included in your MaterialApp or WidgetApp: ```dart @override Widget build(BuildContext context) { return MaterialApp( //******************************************** - navigatorKey: Catcher.navigatorKey, + navigatorKey: Catcher2.navigatorKey, //******************************************** home: Scaffold( appBar: AppBar( @@ -241,35 +259,35 @@ In order to make work Page Report Mode and Dialog Report Mode, you must include } ``` -You need to provide this key, because Catcher needs context of navigator to show dialogs/pages. There is no need to include this navigator key if you won't use Page/Dialog Report Mode. -You can also provide your own navigator key if need to. You can provide it with Catcher constructor (see below). Please check custom navigator key example to see basic example. +You need to provide this key, because Catcher 2 needs context of navigator to show dialogs/pages. There is no need to include this navigator key if you won't use Page/Dialog Report Mode. +You can also provide your own navigator key if need to. You can provide it with Catcher 2 constructor (see below). Please check custom navigator key example to see basic example. -### Catcher configuration -Catcher instance needs rootWidget or runAppFunction in setup time. Please provide one of it. +### Catcher 2 configuration +Catcher 2 instance needs rootWidget or runAppFunction in setup time. Please provide one of it. * rootWidget (optional) - instance of your root application widget * runAppFunction (optional) - function where runApp() will be called -* debugConfig (optional) - config used when Catcher detects that application runs in debug mode -* releaseConfig (optional) - config used when Catcher detects that application runs in release mode -* profileConfig (optional) - config used when Catcher detects that application runs in profile mode -* enableLogger (optional) - enable/disable internal Catcher logs -* navigatorKey (optional) - provide optional navigator key from outside of Catcher -* ensureInitialized (optional) - should Catcher run WidgetsFlutterBinding.ensureInitialized() during initialization +* debugConfig (optional) - config used when Catcher 2 detects that application runs in debug mode +* releaseConfig (optional) - config used when Catcher 2 detects that application runs in release mode +* profileConfig (optional) - config used when Catcher 2 detects that application runs in profile mode +* enableLogger (optional) - enable/disable internal Catcher 2 logs +* navigatorKey (optional) - provide optional navigator key from outside of Catcher 2 +* ensureInitialized (optional) - should Catcher 2 run WidgetsFlutterBinding.ensureInitialized() during initialization ```dart main() { - CatcherOptions debugOptions = - CatcherOptions(DialogReportMode(), [ConsoleHandler()]); - CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [ + Catcher2Options debugOptions = + Catcher2Options(DialogReportMode(), [ConsoleHandler()]); + Catcher2Options releaseOptions = Catcher2Options(DialogReportMode(), [ EmailManualHandler(["recipient@email.com"]) ]); - CatcherOptions profileOptions = CatcherOptions( + Catcher2Options profileOptions = Catcher2Options( NotificationReportMode(), [ConsoleHandler(), ToastHandler()], handlerTimeout: 10000, customParameters: {"example"c: "example_parameter"},); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, profileConfig: profileOptions, enableLogger: false, navigatorKey: navigatorKey); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, profileConfig: profileOptions, enableLogger: false, navigatorKey: navigatorKey); } ``` -CatcherOptions parameters: +Catcher2Options parameters: handlers - list of handlers, which will process report, see handlers to get more information. handlerTimeout - timeout in milliseconds, this parameter describes max time of handling report by handler. reportMode - describes how error report will be shown to user, see report modes to get more information. @@ -281,29 +299,31 @@ handleSilentError - should handle silent errors reported, see FlutterErrorDetail screenshotsPath - path where screenshots will be saved. excludedParameters - parameters which will be excluded from report. filterFunction - function used to filter errors which shouldn't be handled. +onFlutterError - additional error handler for Flutter errors. Set this to `FlutterError.onError` when using Catcher 2 within test suites. +onPlatformError - additional error handler for platform errors. Set this to `PlatformDispatcher.instance.onError` when using Catcher 2 within test suites. -### Report catched exception -Catcher won't process exceptions catched in try/catch block. You can send exception from try catch block to Catcher: +### Report caught exception +Catcher 2 won't process exceptions caught in try/catch block. You can send exception from try catch block to Catcher 2: ```dart try { ... } catch (error,stackTrace) { - Catcher.reportCheckedError(error, stackTrace); + Catcher2.reportCheckedError(error, stackTrace); } ``` ### Localization -Catcher allows to create localizations for Report modes. To add localization support, you need setup +Catcher 2 allows to create localizations for Report modes. To add localization support, you need setup few things: -Add navigatorKey in your MaterialApp: +Add navigatorKey in your `MaterialApp`: ```dart - navigatorKey: Catcher.navigatorKey, + navigatorKey: Catcher2.navigatorKey, ``` -Add flutter localizations delegates and locales in your MaterialApp: +Add Flutter localizations delegates and locales in your MaterialApp: ```dart localizationsDelegates: [ GlobalMaterialLocalizations.delegate, @@ -315,9 +335,9 @@ Add flutter localizations delegates and locales in your MaterialApp: ], ``` -Add localizationOptions in catcherOptions: +Add localizationOptions in `Catcher2Options`: ```dart -CatcherOptions( +Catcher2Options( ... localizationOptions: [ LocalizationOptions("pl", notificationReportModeTitle: "My translation" ...), @@ -340,7 +360,7 @@ You can add translate for given parameters: * toastHandlerDescription - toast handler message -If you want to override default english texts, just add simply localization options for "en" language. +If you want to override default English texts, just simply add localization options for "en" language. There are build in support for languages: * english @@ -387,19 +407,31 @@ LocalizationOptions.buildDefaultItalianOptions(); ```dart LocalizationOptions.buildDefaultKoreanOptions(); ``` +* german +```dart +LocalizationOptions.buildDefaultGermanOptions(); +``` * dutch ```dart LocalizationOptions.buildDefaultDutchOptions(); ``` +* arabic +```dart +LocalizationOptions.buildDefaultArabicOptions(); +``` +* turkish +```dart +LocalizationOptions.buildDefaultTurkishOptions(); +``` Complete Example: ```dart import 'package:flutter/material.dart'; -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; main() { - CatcherOptions debugOptions = CatcherOptions(DialogReportMode(), [ + Catcher2Options debugOptions = Catcher2Options(DialogReportMode(), [ ConsoleHandler(), HttpHandler(HttpRequestType.post, Uri.parse("https://httpstat.us/200"), printLogs: true) @@ -421,11 +453,11 @@ main() { toastHandlerDescription: "Wystąpił błąd:", ) ]); - CatcherOptions releaseOptions = CatcherOptions(NotificationReportMode(), [ + Catcher2Options releaseOptions = Catcher2Options(NotificationReportMode(), [ EmailManualHandler(["recipient@email.com"]) ]); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); } class MyApp extends StatefulWidget { @@ -442,7 +474,7 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - navigatorKey: Catcher.navigatorKey, + navigatorKey: Catcher2.navigatorKey, localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -487,7 +519,7 @@ ReportMode reportMode = SilentReportMode(); ``` #### Notification Report Mode -Notification Report Mode has been removed because of incompatibility with firebase. Please check local_notifications_example to re-add local notificaitons to your app. +Notification Report Mode has been removed because of incompatibility with firebase. Please check local_notifications_example to re-add local notifications to your app. #### Dialog Report Mode Dialog Report Mode shows dialog with information about error. Dialog has title, description and 2 buttons: Accept and Cancel. Once user clicks on Accept button, report will be pushed to handlers. @@ -500,7 +532,7 @@ See localization options to change default texts.

-
+
Dialog report mode

@@ -518,17 +550,17 @@ See localization options to change default texts.

-
+
Page report mode

### Handlers -Handlers are an last point in error processing flow. They are doing specific task with error report, for example logging report to console. +Handlers are an last point in error processing flow. They are doing specific tasks with error reports, for example logging report to console. #### Console Handler -Console Handler is the default and basic handler. It show crash log in console. Console logger allows you to parametrize log output: +Console Handler is the default and basic handler. It shows crash log in console. Console logger allows you to parametrize log output: ```dart ConsoleHandler( @@ -546,9 +578,9 @@ ConsoleHandler( ```dart I/flutter ( 4820): ------- APP INFO ------- I/flutter ( 4820): version: 1.0 -I/flutter ( 4820): appName: catcher_example +I/flutter ( 4820): appName: catcher_2_example I/flutter ( 4820): buildNumber: 1 -I/flutter ( 4820): packageName: com.jhomlala.catcherexample +I/flutter ( 4820): packageName: com.jhomlala.catcher_2_example I/flutter ( 4820): ``` @@ -581,15 +613,15 @@ I/flutter ( 4820): versionSdk: 28 I/flutter ( 4820): versionSecurityPatch: 2018-08-05 ``` -* enableCustomParameters (optional) - display in log section with custom parameters passed to Catcher constructor +* enableCustomParameters (optional) - display in log section with custom parameters passed to Catcher 2 constructor * enableStackTrace (optional) - display in log section with stack trace: ```dart I/flutter ( 5073): ------- STACK TRACE ------- -I/flutter ( 5073): #0 _MyAppState.generateError (package:catcher_example/main.dart:38:5) +I/flutter ( 5073): #0 _MyAppState.generateError (package:catcher_2_example/main.dart:38:5) I/flutter ( 5073): -I/flutter ( 5073): #1 _MyAppState.build. (package:catcher_example/main.dart:31:69) +I/flutter ( 5073): #1 _MyAppState.build. (package:catcher_2_example/main.dart:31:69) I/flutter ( 5073): #2 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14) I/flutter ( 5073): #3 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:562:30) I/flutter ( 5073): #4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24) @@ -635,7 +667,7 @@ Email Manual Handler parameters: Email handler can be used to send automatically email with error reports. Email handler has multiple configuration parameters. Few of them are required, other are optional. These parameters are required: ```dart - EmailAutoHandler("smtp.gmail.com", 587, "somefakeemail@gmail.com", "Catcher", + EmailAutoHandler("smtp.gmail.com", 587, "somefakeemail@gmail.com", "Catcher 2", "FakePassword", ["myemail@gmail.com"]) ``` We need to setup email smtp server, email account and recipient. Currently, only Gmail was tested and worked. You can try use other email providers, but there can be errors. @@ -644,10 +676,11 @@ List of all parameters: * smtpHost (required) - host address of your email, for example host for gmail is smtp.gmail.com * smtpPort (required) - smtp port of your email, for example port for gmail is 587 -* senderEmail (required) - email from which Catcher will send email (it will be sender of error emails) +* senderEmail (required) - email from which Catcher 2 will send email (it will be sender of error emails) * senderName (required) - name of sender email * senderPassword (required) - password for sender email * recipients (required) - list which contains recipient emails +* senderUsername (optional) - set an explicit username for the SMTP authentication * enableSSL (optional) - if your email provider supports SSL, you can enable this option * enableDeviceParameters (optional) - please look in console handler description * enableApplicationParameters (optional) - please look in console handler description @@ -660,7 +693,7 @@ List of all parameters: Example email:

- +

#### Http Handler @@ -683,13 +716,13 @@ All parameters list: * enableCustomParameters (optional) - please look in console handler description You can try using example backend server which handles logs. It's written in Java 8 and Spring Framework and uses material design. -You can find code of backend server here: https://github.com/jhomlala/catcher/tree/master/backend +You can find code of backend server here: https://github.com/ThexXTURBOXx/catcher_2/tree/master/backend

- +

-Note: Remeber to add Internet permission in Android Manifest: +Note: Remember to add Internet permission in Android Manifest: ```xml ``` @@ -700,12 +733,12 @@ File handler allows to store logs in file. Minimal example: ```dart main() { String path = "/storage/emulated/0/log.txt"; - CatcherOptions debugOptions = CatcherOptions( + Catcher2Options debugOptions = Catcher2Options( DialogReportMode(), [FileHandler(File(path), printLogs: true)]); - CatcherOptions releaseOptions = - CatcherOptions(DialogReportMode(), [FileHandler(File(path))]); + Catcher2Options releaseOptions = + Catcher2Options(DialogReportMode(), [FileHandler(File(path))]); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); } ``` @@ -718,13 +751,13 @@ All parameters list: * printLogs (optional) - enable/disable debug logs * handleWhenRejected - please look in console handler description -Example of logging to file in external directory: https://github.com/jhomlala/catcher/blob/master/example/lib/file_example.dart +Example of logging to file in external directory: https://github.com/ThexXTURBOXx/catcher_2/blob/master/example/lib/file_example.dart -If you want to get file path with path_provider lib, you need to call Catcher constructor with -ensureInitialized = true. Then you need to pass your catcher config with updateConfig. +If you want to get file path with path_provider lib, you need to call Catcher 2 constructor with +ensureInitialized = true. Then you need to pass your catcher 2 config with updateConfig. This is required because WidgetBindings ensureInitialized must be called first before accessing path_provider methods. -See example here: https://github.com/jhomlala/catcher/blob/master/example/lib/file_example.dart +See example here: https://github.com/ThexXTURBOXx/catcher_2/blob/master/example/lib/file_example.dart #### Toast Handler Toast handler allows to show short message in toast. Minimal example: @@ -735,11 +768,11 @@ All parameters list: * backgroundColor (optional) - background color of toast * textColor (optional) - text color of toast * fontSize (optional) - text size -* customMessage (optional) - custom message for toast, if not set then "Error occured: error" will be displayed. +* customMessage (optional) - custom message for toast, if not set then "Error occurred: error" will be displayed. * handleWhenRejected - please look in console handler description

- +

#### Sentry Handler @@ -749,13 +782,13 @@ Sentry.io page and then copy DSN link. Example: ```dart main() { - CatcherOptions debugOptions = CatcherOptions( + Catcher2Options debugOptions = Catcher2Options( DialogReportMode(), [SentryHandler(SentryClient("YOUR_DSN_HERE"))]); - CatcherOptions releaseOptions = CatcherOptions(NotificationReportMode(), [ + Catcher2Options releaseOptions = Catcher2Options(NotificationReportMode(), [ EmailManualHandler(["recipient@email.com"]) ]); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); } ``` @@ -764,8 +797,8 @@ All parameters list: * enableDeviceParameters (optional) - please look in console handler description * enableApplicationParameters (optional) - please look in console handler description * enableCustomParameters (optional) - please look in console handler description -* customEnvironment (optional) - custom environment string, if null, Catcher will generate it -* customRelease (optional) - custom release string , if null, Catcher will generate it +* customEnvironment (optional) - custom environment string, if null, Catcher 2 will generate it +* customRelease (optional) - custom release string , if null, Catcher 2 will generate it * printLogs (optional) - enable/disable debug logs #### Slack Handler @@ -776,11 +809,11 @@ works: https://api.slack.com/incoming-webhooks. ```dart main() { - CatcherOptions debugOptions = CatcherOptions(SilentReportMode(), [ + Catcher2Options debugOptions = Catcher2Options(SilentReportMode(), [ SlackHandler( "", - "#catcher", - username: "CatcherTest", + "#catcher2", + username: "Catcher2Test", iconEmoji: ":thinking_face:", enableDeviceParameters: true, enableApplicationParameters: true, @@ -788,13 +821,15 @@ main() { enableStackTrace: true, printLogs: true), ]); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions); } ``` All parameters list: * webhookUrl (required) - url of your webhook -* channel (required) - your channel name (i.e. #catcher) +* channel (required) - your channel name (e.g. #catcher2) +* apiToken (optional) - your API token, only needed for screenshots (e.g. xxxx-xxxxxxxxx-xxxx) +* channelId (optional) - your screenshot channel ID, only needed for screenshots (e.g. C0NF841BK) * username (optional) - name of the integration bot * iconEmoji (optional) - avatar of the integration bot * enableDeviceParameters (optional) - please look in console handler description @@ -810,7 +845,7 @@ works: https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webho ```dart main() { - CatcherOptions debugOptions = CatcherOptions(SilentReportMode(), [ + Catcher2Options debugOptions = Catcher2Options(SilentReportMode(), [ DiscordHandler( "", enableDeviceParameters: true, @@ -820,7 +855,7 @@ main() { printLogs: true), ]); - Catcher(rootWidget: MyApp(), debugConfig: debugOptions); + Catcher2(rootWidget: MyApp(), debugConfig: debugOptions); } ``` @@ -839,7 +874,7 @@ Snackbar handler allows to show customized snackbar message. ```dart void main() { - CatcherOptions debugOptions = CatcherOptions(DialogReportMode(), [ + Catcher2Options debugOptions = Catcher2Options(DialogReportMode(), [ SnackbarHandler( Duration(seconds: 5), backgroundColor: Colors.green, @@ -860,7 +895,7 @@ void main() { ), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(MyApp()); }, @@ -894,7 +929,7 @@ Crashlytics handler has been removed from core package. You can re-enable it in Explicit exception report handler map allows you to setup report handler for specific exception. For example if you want to setup Console Handler for FormatException, you can write: ```dart var explicitMap = {"FormatException": ConsoleHandler()}; -CatcherOptions debugOptions = CatcherOptions( +Catcher2Options debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), @@ -904,13 +939,13 @@ CatcherOptions debugOptions = CatcherOptions( explicitExceptionHandlersMap: explicitMap); ``` -Now if `FormatException` will be catched, then Console Handler will be used. Warning: if you setup explicit exception map for specific exception, then only this handler will be used for this exception! +Now if `FormatException` will be caught, then Console Handler will be used. Warning: if you setup explicit exception map for specific exception, then only this handler will be used for this exception! ### Explicit exception report mode map Same as explicit report handler map, but it's for report mode. Let's say you want to use specific report mode for some exception: ```dart var explicitReportModesMap = {"FormatException": NotificationReportMode()}; - CatcherOptions debugOptions = CatcherOptions( + Catcher2Options debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), @@ -919,7 +954,7 @@ Same as explicit report handler map, but it's for report mode. Let's say you wan ], explicitExceptionReportModesMap: explicitReportModesMap,); ``` -When `FormatException` will be catched, then NotificationReportMode will be used. For other exceptions, Catcher will use DialogReportMode. +When `FormatException` will be caught, then NotificationReportMode will be used. For other exceptions, Catcher 2 will use DialogReportMode. @@ -929,10 +964,10 @@ You can add error widget which will replace red screen of death. To add this int @override Widget build(BuildContext context) { return MaterialApp( - navigatorKey: Catcher.navigatorKey, + navigatorKey: Catcher2.navigatorKey, //******************************************** builder: (BuildContext context, Widget widget) { - Catcher.addDefaultErrorWidget( + Catcher2.addDefaultErrorWidget( showStacktrace: true, title: "Custom error title", description: "Custom error description", @@ -948,7 +983,7 @@ You can add error widget which will replace red screen of death. To add this int ); } ``` -You need to add in your MaterialApp or CupertinoApp builder method with ```Catcher.addDefaultErrorWidget()```. This will add error handler for each widget in your app. +You need to add in your MaterialApp or CupertinoApp builder method with ```Catcher2.addDefaultErrorWidget()```. This will add error handler for each widget in your app. You can provide optional parameters: * showStacktrace - show/hide stacktrace @@ -964,7 +999,7 @@ Error widget will replace your widget if he fails to render. If width of widget With error widgetWithout error widget - +

@@ -972,7 +1007,7 @@ Error widget will replace your widget if he fails to render. If width of widget ### Current config You can get currently used config by using: ```dart -CatcherOptions options = catcher.getCurrentConfig(); +Catcher2Options options = catcher2.getCurrentConfig(); ``` This can be used for example to change custom parameters in runtime. @@ -980,16 +1015,16 @@ This can be used for example to change custom parameters in runtime. Send test exception: ```dart -Catcher.sendTestException(); +Catcher2.sendTestException(); ``` ### Update config -You can update Catcher config during runtime: +You can update Catcher 2 config during runtime: ```dart -///Catcher instance initialized -Catcher catcher; -catcher.updateConfig( - debugConfig: CatcherOptions( +/// Catcher 2 instance initialized +Catcher2 catcher2; +catcher2.updateConfig( + debugConfig: Catcher2Options( PageReportMode(), [ConsoleHandler()], ), @@ -997,13 +1032,13 @@ catcher.updateConfig( ``` ### Screenshots -Catcher can create screenshots automatically and include them in report handlers. To add screenshot -support in your app, simply wrap your root widget with CatcherScreenshot widget: +Catcher 2 can create screenshots automatically and include them in report handlers. To add screenshot +support in your app, simply wrap your root widget with Catcher2Screenshot widget: ```dart MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: CatcherScreenshot( - catcher: Catcher.getInstance(), + navigatorKey: Catcher2.navigatorKey, + home: Catcher2Screenshot( + catcher2: Catcher2.getInstance(), child: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), @@ -1013,10 +1048,10 @@ MaterialApp( ), ); ``` -Also you need to provide directory path, where Catcher will store screenshot files: +Also you need to provide directory path, where Catcher 2 will store screenshot files: ```dart - CatcherOptions debugOptions = CatcherOptions( + Catcher2Options debugOptions = Catcher2Options( DialogReportMode(), [ ToastHandler(), diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..b57e2814 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the newest version, as indicated [here](https://pub.dev/packages/catcher_2/versions) is supported. + +## Reporting a Vulnerability + +You can report a vulnerability using my contact information from [here](https://nmexis.me/). + +If you have reported a vulnerability, I should usually respond within a day. diff --git a/analysis_options.yaml b/analysis_options.yaml index 6b181fb5..f9974823 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,16 +1,266 @@ -include: package:very_good_analysis/analysis_options.yaml +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: ignore + # Ignore imports due to f***ed up import hierarchies. + unnecessary_import: ignore + exclude: + - "bin/cache/**" + # the following two are relative to the stocks example and the flutter package respectively + # see https://github.com/dart-lang/sdk/issues/28463 + - "lib/i18n/messages_*.dart" + - "lib/src/http/**" linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. rules: - public_member_api_docs: false - flutter_style_todos: false - avoid_final_parameters: false - sort_constructors_first: false - avoid_function_literals_in_foreach_calls: false - avoid_positional_boolean_parameters: false - use_if_null_to_convert_nulls_to_bools: false - use_build_context_synchronously: false - prefer_constructors_over_static_methods: false - use_setters_to_change_properties: false - avoid_print: false - sort_pub_dependencies: false \ No newline at end of file + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first + # - always_specify_types + - always_use_package_imports + - annotate_overrides + - annotate_redeclares + - avoid_annotating_with_dynamic + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses + - avoid_catching_errors + # - avoid_classes_with_only_static_members + - avoid_double_and_int_checks + # - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_final_parameters + - avoid_function_literals_in_foreach_calls + - avoid_implementing_value_types + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_multiple_declarations_per_line + # - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + - avoid_private_typedef_functions + # - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_returning_this + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_types_on_closure_parameters + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - avoid_web_libraries_in_flutter + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - cascade_invocations + - cast_nullable_to_non_nullable + - close_sinks + - collection_methods_unrelated_type + - combinators_ordering + - comment_references + - conditional_uri_does_not_exist + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - dangling_library_doc_comments + - depend_on_referenced_packages + - deprecated_consistency + - deprecated_member_use_from_same_package + # - diagnostic_describe_all_properties + - directives_ordering + # - discarded_futures + - do_not_use_environment + - empty_catches + - empty_constructor_bodies + - empty_statements + - eol_at_end_of_file + - exhaustive_cases + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - implicit_call_tearoffs + - implicit_reopen + - invalid_case_patterns + - invalid_runtime_check_with_js_interop_types + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_annotations + - library_names + - library_prefixes + - library_private_types_in_public_api + - lines_longer_than_80_chars + - literal_only_boolean_expressions + - matching_super_parameters + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + # - no_default_cases + - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - no_literal_bool_comparisons + - no_logic_in_create_state + - no_runtimeType_toString + - no_self_assignments + - no_wildcard_variable_uses + - non_constant_identifier_names + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + - omit_local_variable_types + - one_member_abstracts + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + # - prefer_const_constructors + - prefer_const_constructors_in_immutables + # - prefer_const_declarations + # - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + # - prefer_double_quotes + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + # - prefer_final_parameters + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_method_calls + - prefer_null_aware_operators + # - prefer_relative_imports + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + # - public_member_api_docs + - recursive_getters + - require_trailing_commas + - secure_pubspec_urls + - sized_box_for_whitespace + - sized_box_shrink_expand + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + # - type_annotate_public_apis + - type_init_formals + - type_literal_in_constant_pattern + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_breaks + - unnecessary_const + - unnecessary_constructor_name + # - unnecessary_final + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_late + - unnecessary_library_directive + - unnecessary_library_name + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_aware_operator_on_extension_on_nullable + - unnecessary_null_checks + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unnecessary_to_list_in_spreads + - unreachable_from_main + - unrelated_type_equality_checks + - unsafe_html + - use_build_context_synchronously + - use_colored_box + - use_decorated_box + - use_enums + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_if_null_to_convert_nulls_to_bools + - use_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_string_in_part_of_directives + - use_super_parameters + - use_test_throws_matchers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/backend/.mvn/wrapper/maven-wrapper.jar b/backend/.mvn/wrapper/maven-wrapper.jar index 01e67997..cb28b0e3 100644 Binary files a/backend/.mvn/wrapper/maven-wrapper.jar and b/backend/.mvn/wrapper/maven-wrapper.jar differ diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties index cd0d451c..ac184013 100644 --- a/backend/.mvn/wrapper/maven-wrapper.properties +++ b/backend/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,18 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/backend/mvnw b/backend/mvnw index 5551fde8..8d937f4c 100644 --- a/backend/mvnw +++ b/backend/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -36,6 +35,10 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi @@ -50,7 +53,7 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true @@ -58,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -68,69 +71,38 @@ esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -146,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -160,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -181,76 +150,99 @@ find_maven_basedir() { fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$(cd "$wdir/.." || exit 1; pwd) fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi - wget "$jarUrl" -O "$wrapperJarPath" elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - curl -o "$wrapperJarPath" "$jarUrl" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi @@ -259,28 +251,58 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/backend/mvnw.cmd b/backend/mvnw.cmd index e5cfb0ae..c4586b56 100644 --- a/backend/mvnw.cmd +++ b/backend/mvnw.cmd @@ -18,15 +18,14 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -37,7 +36,7 @@ @echo off @REM set title of command window title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -46,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal @@ -120,24 +119,69 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) ) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) ) @REM End of extension -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end @@ -147,15 +191,15 @@ set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause +if "%MAVEN_BATCH_PAUSE%"=="on" pause -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% -exit /B %ERROR_CODE% +cmd /C exit /B %ERROR_CODE% diff --git a/backend/pom.xml b/backend/pom.xml index 17f7ec64..fef03929 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -6,17 +6,17 @@ org.springframework.boot spring-boot-starter-parent - 2.1.2.RELEASE + 3.1.4 - com.jhomlala.catcher + com.jhomlala.catcher_2 backend 0.0.1-SNAPSHOT backend - Backend for Catcher plugin + Backend for Catcher 2 plugin - 1.8 + 8 diff --git a/backend/src/main/java/com/jhomlala/catcher/backend/BackendApplication.java b/backend/src/main/java/com/jhomlala/catcher_2/backend/BackendApplication.java similarity index 87% rename from backend/src/main/java/com/jhomlala/catcher/backend/BackendApplication.java rename to backend/src/main/java/com/jhomlala/catcher_2/backend/BackendApplication.java index 3f18a3d1..f4754dba 100644 --- a/backend/src/main/java/com/jhomlala/catcher/backend/BackendApplication.java +++ b/backend/src/main/java/com/jhomlala/catcher_2/backend/BackendApplication.java @@ -1,4 +1,4 @@ -package com.jhomlala.catcher.backend; +package com.jhomlala.catcher_2.backend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLog.java b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLog.java similarity index 98% rename from backend/src/main/java/com/jhomlala/catcher/backend/ReportLog.java rename to backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLog.java index 65418ab7..fe124701 100644 --- a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLog.java +++ b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLog.java @@ -1,4 +1,4 @@ -package com.jhomlala.catcher.backend; +package com.jhomlala.catcher_2.backend; import java.sql.Timestamp; import java.util.Map; diff --git a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLogController.java b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogController.java similarity index 96% rename from backend/src/main/java/com/jhomlala/catcher/backend/ReportLogController.java rename to backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogController.java index e067625d..1f20b4b9 100644 --- a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLogController.java +++ b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogController.java @@ -1,4 +1,4 @@ -package com.jhomlala.catcher.backend; +package com.jhomlala.catcher_2.backend; import java.util.ArrayList; import java.util.Collections; diff --git a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLogService.java b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogService.java similarity index 89% rename from backend/src/main/java/com/jhomlala/catcher/backend/ReportLogService.java rename to backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogService.java index 2b2734d8..b9c70f42 100644 --- a/backend/src/main/java/com/jhomlala/catcher/backend/ReportLogService.java +++ b/backend/src/main/java/com/jhomlala/catcher_2/backend/ReportLogService.java @@ -1,4 +1,4 @@ -package com.jhomlala.catcher.backend; +package com.jhomlala.catcher_2.backend; import java.util.ArrayList; import java.util.List; diff --git a/backend/src/main/resources/templates/reports.html b/backend/src/main/resources/templates/reports.html index ea3cb2bb..00a534c9 100644 --- a/backend/src/main/resources/templates/reports.html +++ b/backend/src/main/resources/templates/reports.html @@ -5,9 +5,9 @@ - - + Reports diff --git a/backend/src/test/java/com/jhomlala/catcher/backend/BackendApplicationTests.java b/backend/src/test/java/com/jhomlala/catcher/backend/BackendApplicationTests.java deleted file mode 100644 index 2454f25f..00000000 --- a/backend/src/test/java/com/jhomlala/catcher/backend/BackendApplicationTests.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.jhomlala.catcher.backend; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class BackendApplicationTests { - - @Test - public void contextLoads() { - } - -} - diff --git a/backend/src/test/java/com/jhomlala/catcher_2/backend/BackendApplicationTests.java b/backend/src/test/java/com/jhomlala/catcher_2/backend/BackendApplicationTests.java new file mode 100644 index 00000000..44be30a0 --- /dev/null +++ b/backend/src/test/java/com/jhomlala/catcher_2/backend/BackendApplicationTests.java @@ -0,0 +1,17 @@ +package com.jhomlala.catcher_2.backend; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest +public class BackendApplicationTests { + + @Test + public void contextLoads() { + } + +} + diff --git a/catcher.iml b/catcher.iml deleted file mode 100644 index bf68c1a8..00000000 --- a/catcher.iml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example/.metadata b/example/.metadata deleted file mode 100644 index cbf1dc0e..00000000 --- a/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: android - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: ios - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: linux - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: macos - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: web - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: windows - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/README.md b/example/README.md index 50e9ac24..33440f62 100644 --- a/example/README.md +++ b/example/README.md @@ -1,6 +1,6 @@ -# example +# catcher_2_example -A project to showcase Catcher. +Demonstrates how to use the catcher_2 plugin. ## Getting Started diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 6b181fb5..0d290213 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1,16 +1,28 @@ -include: package:very_good_analysis/analysis_options.yaml +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. rules: - public_member_api_docs: false - flutter_style_todos: false - avoid_final_parameters: false - sort_constructors_first: false - avoid_function_literals_in_foreach_calls: false - avoid_positional_boolean_parameters: false - use_if_null_to_convert_nulls_to_bools: false - use_build_context_synchronously: false - prefer_constructors_over_static_methods: false - use_setters_to_change_properties: false - avoid_print: false - sort_pub_dependencies: false \ No newline at end of file + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore index 6f568019..7760dbbd 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,8 +1,5 @@ -gradle-wrapper.jar /.gradle /captures/ -/gradlew -/gradlew.bat /local.properties GeneratedPluginRegistrant.java diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7ffe18a8..ab530b3e 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -24,18 +24,23 @@ if (flutterVersionName == null) { } android { - namespace = "com.jhomlala.example" + namespace = "com.jhomlala.catcher_2_example" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "25.1.8937393" // flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + coreLibraryDesugaringEnabled true + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.jhomlala.example" + applicationId = "com.jhomlala.catcher_2_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdk = flutter.minSdkVersion @@ -56,3 +61,7 @@ android { flutter { source = "../.." } + +dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' +} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 74a78b93..e9d8d817 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -77,84 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat index 8a0b282a..9d21a218 100644 --- a/example/android/gradlew.bat +++ b/example/android/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 536165d3..dfbdd5fe 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false + id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "1.7.10" apply false } diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bef642cc..04c584e1 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -384,7 +384,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -401,7 +401,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -416,7 +416,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -547,7 +547,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -569,7 +569,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 5458fc41..3dce7e7d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Example + Catcher 2 CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + catcher_2_example CFBundlePackageType APPL CFBundleShortVersionString diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift index 86a7c3b1..eb8874d4 100644 --- a/example/ios/RunnerTests/RunnerTests.swift +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -2,11 +2,25 @@ import Flutter import UIKit import XCTest +@testable import catcher_2 + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + class RunnerTests: XCTestCase { - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + func testGetPlatformVersion() { + let plugin = Catcher_2Plugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) } } diff --git a/example/lib/basic_example.dart b/example/lib/basic_example.dart index d0216c18..476ed031 100644 --- a/example/lib/basic_example.dart +++ b/example/lib/basic_example.dart @@ -1,15 +1,15 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -22,9 +22,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -34,31 +32,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/change_custom_parameters_example.dart b/example/lib/change_custom_parameters_example.dart index 67d1c82e..a45b926f 100644 --- a/example/lib/change_custom_parameters_example.dart +++ b/example/lib/change_custom_parameters_example.dart @@ -1,23 +1,23 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; -late Catcher catcher; +late Catcher2 catcher2; void main() { final customParameters = {}; customParameters['First'] = 'First parameter'; - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( PageReportMode(), [ ConsoleHandler(enableCustomParameters: true), ], customParameters: customParameters, ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - catcher = Catcher( + catcher2 = Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -28,9 +28,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -40,44 +38,40 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return Column( - children: [ - ElevatedButton( - onPressed: _changeCustomParameters, - child: const Text('Change custom parameters'), - ), - ElevatedButton( - onPressed: generateError, - child: const Text('Generate error'), - ), - ], - ); - } + Widget build(BuildContext context) => Column( + children: [ + ElevatedButton( + onPressed: _changeCustomParameters, + child: const Text('Change custom parameters'), + ), + ElevatedButton( + onPressed: generateError, + child: const Text('Generate error'), + ), + ], + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } void _changeCustomParameters() { - final options = catcher.getCurrentConfig()!; + final options = catcher2.getCurrentConfig()!; options.customParameters['Second'] = 'Second parameter'; } } diff --git a/example/lib/crashlytics_example.dart b/example/lib/crashlytics_example.dart index 85a72e73..54cd261e 100644 --- a/example/lib/crashlytics_example.dart +++ b/example/lib/crashlytics_example.dart @@ -1,10 +1,10 @@ -/*import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +/*import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; class CrashlyticsHandler extends ReportHandler { final Logger _logger = Logger("CrashlyticsHandler"); @@ -44,11 +44,11 @@ class CrashlyticsHandler extends ReportHandler { crashlytics.log(_getLogMessage(report)); if (report.errorDetails != null) { // ignore: cast_nullable_to_non_nullable - await crashlytics.recordFlutterError(report.errorDetails as - FlutterErrorDetails); + await crashlytics.recordFlutterError( + report.errorDetails as FlutterErrorDetails); } else { - await crashlytics.recordError(report.error, report.stackTrace as - StackTrace); + await crashlytics.recordError(report.error, + report.stackTrace as StackTrace); } _printLog("Crashlytics report sent"); return true; @@ -90,15 +90,15 @@ class CrashlyticsHandler extends ReportHandler { main() { - CatcherOptions debugOptions = CatcherOptions(DialogReportMode(), [ + Catcher2Options debugOptions = Catcher2Options(DialogReportMode(), [ CrashlyticsHandler(), ConsoleHandler() ]); - CatcherOptions releaseOptions = CatcherOptions(PageReportMode(), [ + Catcher2Options releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(["recipient@email.com"]) ]); - Catcher(MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); + Catcher2(MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions); } class MyApp extends StatefulWidget { @@ -115,7 +115,7 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - navigatorKey: Catcher.navigatorKey, + navigatorKey: Catcher2.navigatorKey, home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), @@ -137,6 +137,6 @@ class ChildWidget extends StatelessWidget { } void generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } }*/ diff --git a/example/lib/cupertino_example.dart b/example/lib/cupertino_example.dart index aea79cbd..0953ea55 100644 --- a/example/lib/cupertino_example.dart +++ b/example/lib/cupertino_example.dart @@ -1,9 +1,9 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ //EmailManualHandler(["recipient@email.com"]), HttpHandler( HttpRequestType.post, @@ -12,11 +12,11 @@ void main() { ), ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -27,9 +27,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -39,36 +37,32 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return CupertinoApp( - navigatorKey: Catcher.navigatorKey, - home: const CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text('Cupertino example'), - ), - child: SafeArea( - child: ChildWidget(), + Widget build(BuildContext context) => CupertinoApp( + navigatorKey: Catcher2.navigatorKey, + home: const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('Cupertino example'), + ), + child: SafeArea( + child: ChildWidget(), + ), ), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return ColoredBox( - color: Colors.orange, - child: TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ), - ); - } + Widget build(BuildContext context) => ColoredBox( + color: Colors.orange, + child: TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/custom_logger_example.dart b/example/lib/custom_logger_example.dart index 784aa238..fd939559 100644 --- a/example/lib/custom_logger_example.dart +++ b/example/lib/custom_logger_example.dart @@ -1,19 +1,19 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), ], - logger: CustomCatcherLogger(), + logger: CustomCatcher2Logger(), ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -26,9 +26,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -38,53 +36,53 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } -class CustomCatcherLogger extends CatcherLogger { +class CustomCatcher2Logger extends Catcher2Logger { @override void info(String message) { + // ignore: avoid_print print('Custom Catcher Logger | Info | $message'); } @override void fine(String message) { + // ignore: avoid_print print('Custom Catcher Logger | Fine | $message'); } @override void warning(String message) { + // ignore: avoid_print print('Custom Catcher Logger | Warning | $message'); } @override void severe(String message) { - print('Custom Catcher Logger | Servere | $message'); + // ignore: avoid_print + print('Custom Catcher Logger | Severe | $message'); } } diff --git a/example/lib/custom_navigator_key_example.dart b/example/lib/custom_navigator_key_example.dart index 15f7174a..f06ceffa 100644 --- a/example/lib/custom_navigator_key_example.dart +++ b/example/lib/custom_navigator_key_example.dart @@ -1,17 +1,17 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ EmailManualHandler(['recipient@email.com']), ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); final navigatorKey = GlobalKey(); - Catcher( + Catcher2( rootWidget: MyApp(navigatorKey), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -20,14 +20,12 @@ void main() { } class MyApp extends StatefulWidget { - final GlobalKey navigatorKey; - const MyApp(this.navigatorKey, {super.key}); + final GlobalKey navigatorKey; + @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -37,31 +35,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: widget.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: widget.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/custom_report_mode_example.dart b/example/lib/custom_report_mode_example.dart index 898bf035..f7e87b44 100644 --- a/example/lib/custom_report_mode_example.dart +++ b/example/lib/custom_report_mode_example.dart @@ -1,17 +1,17 @@ -import 'package:catcher/catcher.dart'; -import 'package:catcher/model/platform_type.dart'; +import 'package:catcher_2/catcher_2.dart'; +import 'package:catcher_2/model/platform_type.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(CustomPageReportMode(), [ + final debugOptions = Catcher2Options(CustomPageReportMode(), [ EmailManualHandler(['recipient@email.com']), ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -22,9 +22,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -34,32 +32,28 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } @@ -76,6 +70,9 @@ class CustomPageReportMode extends ReportMode { BuildContext context, ) async { await Future.delayed(Duration.zero); + if (!context.mounted) { + return; + } await Navigator.push( context, MaterialPageRoute(builder: (context) => CustomPage(this, report)), @@ -83,9 +80,7 @@ class CustomPageReportMode extends ReportMode { } @override - bool isContextRequired() { - return true; - } + bool isContextRequired() => true; @override List getSupportedPlatforms() => @@ -93,34 +88,31 @@ class CustomPageReportMode extends ReportMode { } class CustomPage extends StatelessWidget { + const CustomPage(this.reportMode, this.report, {super.key}); final ReportMode reportMode; final Report report; - const CustomPage(this.reportMode, this.report, {super.key}); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Test'), - ), - body: Row( - children: [ - ElevatedButton( - child: const Text('Send report'), - onPressed: () { - reportMode.onActionConfirmed(report); - }, - ), - ElevatedButton( - child: const Text('Cancel report'), - onPressed: () { - reportMode.onActionRejected(report); - Navigator.pop(context); - }, - ), - ], - ), - ); - } + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: const Text('Test'), + ), + body: Row( + children: [ + ElevatedButton( + child: const Text('Send report'), + onPressed: () { + reportMode.onActionConfirmed(report); + }, + ), + ElevatedButton( + child: const Text('Cancel report'), + onPressed: () { + reportMode.onActionRejected(report); + Navigator.pop(context); + }, + ), + ], + ), + ); } diff --git a/example/lib/discord_handler_example.dart b/example/lib/discord_handler_example.dart index f4c97698..80eaccf4 100644 --- a/example/lib/discord_handler_example.dart +++ b/example/lib/discord_handler_example.dart @@ -1,8 +1,8 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(SilentReportMode(), [ + final debugOptions = Catcher2Options(SilentReportMode(), [ DiscordHandler( '', enableDeviceParameters: true, @@ -12,11 +12,11 @@ void main() { printLogs: true, ), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -27,9 +27,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -39,31 +37,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/email_manual_handler_example.dart b/example/lib/email_manual_handler_example.dart index 1f722da8..252201b2 100644 --- a/example/lib/email_manual_handler_example.dart +++ b/example/lib/email_manual_handler_example.dart @@ -1,12 +1,17 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ EmailManualHandler( ['email1@email.com', 'email2@email.com'], + enableDeviceParameters: true, + enableStackTrace: true, + enableCustomParameters: true, + enableApplicationParameters: true, + sendHtml: true, emailTitle: 'Sample Title', emailHeader: 'Sample Header', printLogs: true, @@ -18,7 +23,7 @@ void main() { }, ); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, ); @@ -28,9 +33,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -40,29 +43,25 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { throw Exception('Test exception'); diff --git a/example/lib/error_widget_example.dart b/example/lib/error_widget_example.dart index 11cbfafd..a8aa61f9 100644 --- a/example/lib/error_widget_example.dart +++ b/example/lib/error_widget_example.dart @@ -1,15 +1,15 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(SilentReportMode(), [ + final debugOptions = Catcher2Options(SilentReportMode(), [ ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -20,9 +20,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -32,24 +30,24 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - builder: (BuildContext context, Widget? widget) { - Catcher.addDefaultErrorWidget( - title: 'Custom title', - description: 'Custom description', - ); - return widget!; - }, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + builder: (context, widget) { + Catcher2.addDefaultErrorWidget( + showStacktrace: true, + title: 'Custom title', + description: 'Custom description', + maxWidthForSmallMode: 150, + ); + return widget!; + }, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: _buildSmallErrorWidget(), ), - body: _buildSmallErrorWidget(), - ), - ); - } + ); ///Trigger "normal" mode /*Widget _buildNormalErrorWidget() { @@ -57,27 +55,24 @@ class _MyAppState extends State { }*/ ///Trigger "small" mode - Widget _buildSmallErrorWidget() { - return GridView.count( - crossAxisCount: 3, - children: const [ - ChildWidget(), - ChildWidget(), - ChildWidget(), - ], - ); - } + Widget _buildSmallErrorWidget() => GridView.count( + crossAxisCount: 3, + children: const [ + ChildWidget(), + ChildWidget(), + ChildWidget(), + ], + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton(onPressed: generateError, child: const Text('Test')); - } + Widget build(BuildContext context) => + TextButton(onPressed: generateError, child: const Text('Test')); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/excluded_parameters_example.dart b/example/lib/excluded_parameters_example.dart index 8d2abd23..1eb58d01 100644 --- a/example/lib/excluded_parameters_example.dart +++ b/example/lib/excluded_parameters_example.dart @@ -1,8 +1,8 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ //EmailManualHandler(["recipient@email.com"]), @@ -18,11 +18,11 @@ void main() { //Exclude these parameters from report. These params are device info params. excludedParameters: ['androidId', 'model'], ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -35,9 +35,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -47,31 +45,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/explicit_error_report_handler_map_example.dart b/example/lib/explicit_error_report_handler_map_example.dart index e9966e6d..9f7ba04d 100644 --- a/example/lib/explicit_error_report_handler_map_example.dart +++ b/example/lib/explicit_error_report_handler_map_example.dart @@ -1,9 +1,9 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { final explicitMap = {'FormatException': ConsoleHandler()}; - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), @@ -15,11 +15,11 @@ void main() { ], explicitExceptionHandlersMap: explicitMap, ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -30,9 +30,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -42,29 +40,25 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { throw const FormatException('Example Error'); diff --git a/example/lib/explicit_error_report_mode_map_example.dart b/example/lib/explicit_error_report_mode_map_example.dart index 01ee557c..ce27a267 100644 --- a/example/lib/explicit_error_report_mode_map_example.dart +++ b/example/lib/explicit_error_report_mode_map_example.dart @@ -1,9 +1,9 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { final explicitReportModesMap = {'FormatException': PageReportMode()}; - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), @@ -15,11 +15,11 @@ void main() { ], explicitExceptionReportModesMap: explicitReportModesMap, ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -30,9 +30,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -42,37 +40,33 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return Column( - children: [ - TextButton( - onPressed: generateFirstError, - child: const Text('Generate first error'), - ), - TextButton( - onPressed: generateSecondError, - child: const Text('Generate second error'), - ), - ], - ); - } + Widget build(BuildContext context) => Column( + children: [ + TextButton( + onPressed: generateFirstError, + child: const Text('Generate first error'), + ), + TextButton( + onPressed: generateSecondError, + child: const Text('Generate second error'), + ), + ], + ); Future generateFirstError() async { throw const FormatException('Example Error'); diff --git a/example/lib/file_example.dart b/example/lib/file_example.dart index 2922d49d..51544af1 100644 --- a/example/lib/file_example.dart +++ b/example/lib/file_example.dart @@ -1,12 +1,12 @@ import 'dart:io'; -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; void main() async { - final catcher = Catcher(rootWidget: const MyApp(), ensureInitialized: true); + final catcher2 = Catcher2(rootWidget: const MyApp(), ensureInitialized: true); Directory? externalDir; if (Platform.isAndroid || Platform.isIOS) { externalDir = await getExternalStorageDirectory(); @@ -18,14 +18,16 @@ void main() async { if (externalDir != null) { path = '${externalDir.path}/log.txt'; } + // ignore: avoid_print + print('PATH: $path'); - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [FileHandler(File(path), printLogs: true)], ); final releaseOptions = - CatcherOptions(DialogReportMode(), [FileHandler(File(path))]); - catcher.updateConfig( + Catcher2Options(DialogReportMode(), [FileHandler(File(path))]); + catcher2.updateConfig( debugConfig: debugOptions, releaseConfig: releaseOptions, ); @@ -35,9 +37,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -47,42 +47,40 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return Column( - children: [ - TextButton( - onPressed: checkPermissions, - child: const Text('Check permission'), - ), - TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ), - ], - ); - } + Widget build(BuildContext context) => Column( + children: [ + TextButton( + onPressed: checkPermissions, + child: const Text('Check permission'), + ), + TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ), + ], + ); Future checkPermissions() async { final status = await Permission.storage.status; + // ignore: avoid_print print('Status: $status'); if (!status.isGranted) { + // ignore: avoid_print print('Requested'); } } diff --git a/example/lib/filter_example.dart b/example/lib/filter_example.dart index 50bb422f..62e86d2c 100644 --- a/example/lib/filter_example.dart +++ b/example/lib/filter_example.dart @@ -1,14 +1,14 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), ToastHandler(), ], - filterFunction: (Report report) { + filterFunction: (report) { if (report.error is ArgumentError) { return false; } else { @@ -16,11 +16,11 @@ void main() { } }, ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -33,9 +33,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -45,28 +43,26 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Filter example'), - ), - body: Column( - children: [ - TextButton( - onPressed: generateNormalError, - child: const Text('Generate normal error'), - ), - TextButton( - onPressed: generateFilteredError, - child: const Text('Generate filtered error'), - ), - ], + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Filter example'), + ), + body: Column( + children: [ + TextButton( + onPressed: generateNormalError, + child: const Text('Generate normal error'), + ), + TextButton( + onPressed: generateFilteredError, + child: const Text('Generate filtered error'), + ), + ], + ), ), - ), - ); - } + ); Future generateNormalError() async { throw StateError('Example error'); diff --git a/example/lib/http_handler_update_headers_example.dart b/example/lib/http_handler_update_headers_example.dart index 7f71dfca..3055681d 100644 --- a/example/lib/http_handler_update_headers_example.dart +++ b/example/lib/http_handler_update_headers_example.dart @@ -1,4 +1,4 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { @@ -14,12 +14,12 @@ void main() { ///Init catcher final debugOptions = - CatcherOptions(DialogReportMode(), [httpHandler, ConsoleHandler()]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + Catcher2Options(DialogReportMode(), [httpHandler, ConsoleHandler()]); + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -35,9 +35,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -47,31 +45,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/local_notification_example.dart b/example/lib/local_notification_example.dart index b60ba155..196ca136 100644 --- a/example/lib/local_notification_example.dart +++ b/example/lib/local_notification_example.dart @@ -1,18 +1,18 @@ -import 'package:catcher/catcher.dart'; -import 'package:catcher/model/platform_type.dart'; +import 'package:catcher_2/catcher_2.dart'; +import 'package:catcher_2/model/platform_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; void main() { - final debugOptions = CatcherOptions(NotificationReportMode(), [ + final debugOptions = Catcher2Options(NotificationReportMode(), [ EmailManualHandler(['recipient@email.com']), ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -23,9 +23,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -35,36 +33,38 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } class NotificationReportMode extends ReportMode { + NotificationReportMode({ + this.channelId = 'Catcher 2', + this.channelName = 'Catcher 2', + this.channelDescription = 'Catcher 2 default channel', + this.icon = '@mipmap/ic_launcher', + }); late FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin; late Report _lastReport; @@ -73,17 +73,10 @@ class NotificationReportMode extends ReportMode { final String channelDescription; final String icon; - NotificationReportMode({ - this.channelId = 'Catcher', - this.channelName = 'Catcher', - this.channelDescription = 'Catcher default channel', - this.icon = '@mipmap/ic_launcher', - }); - @override - void setReportModeAction(ReportModeAction reportModeAction) { + set reportModeAction(ReportModeAction reportModeAction) { _initializeNotificationsPlugin(); - return super.setReportModeAction(reportModeAction); + super.reportModeAction = reportModeAction; } /// We need to init notifications plugin after constructor. If we init @@ -98,7 +91,6 @@ class NotificationReportMode extends ReportMode { android: initializationSettingsAndroid, iOS: initializationSettingsIOS, ); - _flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: onSelectedNotification, @@ -111,7 +103,7 @@ class NotificationReportMode extends ReportMode { _sendNotification(); } - Future onSelectedNotification(NotificationResponse response) { + Future onSelectedNotification(NotificationResponse details) { onActionConfirmed(_lastReport); return Future.value(0); } @@ -121,6 +113,8 @@ class NotificationReportMode extends ReportMode { channelId, channelName, channelDescription: channelDescription, + importance: Importance.defaultImportance, + priority: Priority.defaultPriority, ); const iOSPlatformChannelSpecifics = DarwinNotificationDetails(); final platformChannelSpecifics = NotificationDetails( diff --git a/example/lib/localization_example.dart b/example/lib/localization_example.dart index a54bf1f6..fcc289a7 100644 --- a/example/lib/localization_example.dart +++ b/example/lib/localization_example.dart @@ -1,9 +1,9 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ ConsoleHandler(), @@ -25,29 +25,29 @@ void main() { 'pl', notificationReportModeTitle: 'Wystąpił błąd aplikacji', notificationReportModeContent: - 'Naciśnij tutaj aby wysłać report do zespołu wpsarcia', - dialogReportModeTitle: 'Błąd appliance', + 'Naciśnij tutaj aby wysłać raport do zespołu wpsarcia', + dialogReportModeTitle: 'Błąd aplikacji', dialogReportModeDescription: 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest ' - 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj ' - 'aby wysłać raport lub odrzuć aby odrzucić raport.', + 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby ' + 'wysłać raport lub odrzuć aby odrzucić raport.', dialogReportModeAccept: 'Akceptuj', dialogReportModeCancel: 'Odrzuć', pageReportModeTitle: 'Błąd aplikacji', pageReportModeDescription: 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest ' - 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby' - ' wysłać raport lub odrzuć aby odrzucić raport.', + 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby ' + 'wysłać raport lub odrzuć aby odrzucić raport.', pageReportModeAccept: 'Akceptuj', pageReportModeCancel: 'Odrzuć', ), ], ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -58,9 +58,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -70,37 +68,33 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', 'US'), - Locale('pl', 'PL'), - ], - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', 'US'), + Locale('pl', 'PL'), + ], + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { throw Exception('Test exception'); diff --git a/example/lib/main.dart b/example/lib/main.dart index 827f137f..757addbd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,10 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; import 'package:sentry/sentry.dart'; void main() { ///Configure your debug options (settings used in development mode) - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( ///Show information about caught error in dialog DialogReportMode(), [ @@ -21,7 +21,7 @@ void main() { ); ///Configure your production options (settings used in release mode) - final releaseOptions = CatcherOptions( + final releaseOptions = Catcher2Options( ///Show new page with information about caught error PageReportMode(), [ @@ -39,10 +39,11 @@ void main() { ///Start Catcher and then start App. Now Catcher will guard and report any ///error to your configured services! - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, + ensureInitialized: true, debugConfig: debugOptions, releaseConfig: releaseOptions, ); @@ -52,9 +53,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -64,34 +63,30 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - ///Last step: add navigator key of Catcher here, so Catcher can show - ///page and dialog! - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Catcher example'), + Widget build(BuildContext context) => MaterialApp( + ///Last step: add navigator key of Catcher here, so Catcher can show + ///page and dialog! + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Catcher example'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); ///Simply just trigger some error. Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/occurences_timeout_example.dart b/example/lib/occurences_timeout_example.dart index a2adb18a..5adcbe6e 100644 --- a/example/lib/occurences_timeout_example.dart +++ b/example/lib/occurences_timeout_example.dart @@ -1,8 +1,8 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( SilentReportMode(), [ ConsoleHandler(), @@ -10,11 +10,11 @@ void main() { ], reportOccurrenceTimeout: 30000, ); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -27,9 +27,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -39,31 +37,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/report_modes_example.dart b/example/lib/report_modes_example.dart index c3fff8a9..5b805f82 100644 --- a/example/lib/report_modes_example.dart +++ b/example/lib/report_modes_example.dart @@ -1,4 +1,4 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { @@ -18,18 +18,16 @@ void main() { //page: final ReportMode reportMode = PageReportMode(showStackTrace: false); - final debugOptions = CatcherOptions(reportMode, [ConsoleHandler()]); + final debugOptions = Catcher2Options(reportMode, [ConsoleHandler()]); - Catcher(rootWidget: const MyApp(), debugConfig: debugOptions); + Catcher2(rootWidget: const MyApp(), debugConfig: debugOptions); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -39,31 +37,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + throw Exception('Test exception'); } } diff --git a/example/lib/screenshot_example.dart b/example/lib/screenshot_example.dart index 211cc5b9..11a896cb 100644 --- a/example/lib/screenshot_example.dart +++ b/example/lib/screenshot_example.dart @@ -1,11 +1,11 @@ import 'dart:io'; -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; void main() async { - final catcher = Catcher(rootWidget: const MyApp(), ensureInitialized: true); + final catcher2 = Catcher2(rootWidget: const MyApp(), ensureInitialized: true); Directory? externalDir; if (Platform.isAndroid) { externalDir = await getExternalStorageDirectory(); @@ -18,11 +18,16 @@ void main() async { path = externalDir.path; } - final debugOptions = CatcherOptions( + final debugOptions = Catcher2Options( DialogReportMode(), [ EmailManualHandler( ['email1@email.com', 'email2@email.com'], + enableDeviceParameters: true, + enableStackTrace: true, + enableCustomParameters: true, + enableApplicationParameters: true, + sendHtml: true, emailTitle: 'Sample Title', emailHeader: 'Sample Header', printLogs: true, @@ -35,16 +40,14 @@ void main() async { screenshotsPath: path, ); - catcher.updateConfig(debugConfig: debugOptions); + catcher2.updateConfig(debugConfig: debugOptions); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -54,34 +57,30 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: CatcherScreenshot( - catcher: Catcher.getInstance(), - child: const ChildWidget(), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Catcher2Screenshot( + catcher2: Catcher2.getInstance(), + child: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), + ), ), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/sentry_example.dart b/example/lib/sentry_example.dart index 95fbc1bb..c6ce7077 100644 --- a/example/lib/sentry_example.dart +++ b/example/lib/sentry_example.dart @@ -1,18 +1,19 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; import 'package:sentry/sentry.dart'; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ SentryHandler( SentryClient(SentryOptions(dsn: 'YOUR DSN HERE')), + printLogs: true, ), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -23,9 +24,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -35,31 +34,25 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => + TextButton(onPressed: generateError, child: const Text('Generate error')); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/slack_handler_example.dart b/example/lib/slack_handler_example.dart index 896743e3..a9bd59a0 100644 --- a/example/lib/slack_handler_example.dart +++ b/example/lib/slack_handler_example.dart @@ -1,8 +1,8 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(SilentReportMode(), [ + final debugOptions = Catcher2Options(SilentReportMode(), [ SlackHandler( '', '#catcher', @@ -16,11 +16,11 @@ void main() { ), //ConsoleHandler() ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - Catcher( + Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -31,9 +31,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -43,31 +41,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/snackbar_handler_example.dart b/example/lib/snackbar_handler_example.dart index 290fd052..76a0b352 100644 --- a/example/lib/snackbar_handler_example.dart +++ b/example/lib/snackbar_handler_example.dart @@ -1,8 +1,8 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ SnackbarHandler( const Duration(seconds: 5), backgroundColor: Colors.green, @@ -14,6 +14,7 @@ void main() { action: SnackBarAction( label: 'Button', onPressed: () { + // ignore: avoid_print print('Click!'); }, ), @@ -23,7 +24,7 @@ void main() { ), ), ]); - final releaseOptions = CatcherOptions(DialogReportMode(), [ + final releaseOptions = Catcher2Options(DialogReportMode(), [ SnackbarHandler( const Duration(seconds: 5), backgroundColor: Colors.green, @@ -36,7 +37,7 @@ void main() { ), ]); - Catcher( + Catcher2( runAppFunction: () { runApp(const MyApp()); }, @@ -49,9 +50,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -61,31 +60,27 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Snackbar handler example'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Snackbar handler example'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ); - } + Widget build(BuildContext context) => TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } } diff --git a/example/lib/update_config_example.dart b/example/lib/update_config_example.dart index e9f5bd8f..79e6bc66 100644 --- a/example/lib/update_config_example.dart +++ b/example/lib/update_config_example.dart @@ -1,10 +1,10 @@ -import 'package:catcher/catcher.dart'; +import 'package:catcher_2/catcher_2.dart'; import 'package:flutter/material.dart'; -late Catcher catcher; +late Catcher2 catcher2; void main() { - final debugOptions = CatcherOptions(DialogReportMode(), [ + final debugOptions = Catcher2Options(DialogReportMode(), [ //EmailManualHandler(["recipient@email.com"]), HttpHandler( HttpRequestType.post, @@ -13,11 +13,11 @@ void main() { ), ConsoleHandler(), ]); - final releaseOptions = CatcherOptions(PageReportMode(), [ + final releaseOptions = Catcher2Options(PageReportMode(), [ EmailManualHandler(['recipient@email.com']), ]); - catcher = Catcher( + catcher2 = Catcher2( rootWidget: const MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions, @@ -28,9 +28,7 @@ class MyApp extends StatefulWidget { const MyApp({super.key}); @override - State createState() { - return _MyAppState(); - } + State createState() => _MyAppState(); } class _MyAppState extends State { @@ -40,45 +38,41 @@ class _MyAppState extends State { } @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: Catcher.navigatorKey, - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + Widget build(BuildContext context) => MaterialApp( + navigatorKey: Catcher2.navigatorKey, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: const ChildWidget(), ), - body: const ChildWidget(), - ), - ); - } + ); } class ChildWidget extends StatelessWidget { const ChildWidget({super.key}); @override - Widget build(BuildContext context) { - return Row( - children: [ - TextButton( - onPressed: changeConfig, - child: const Text('Change config'), - ), - TextButton( - onPressed: generateError, - child: const Text('Generate error'), - ), - ], - ); - } + Widget build(BuildContext context) => Row( + children: [ + TextButton( + onPressed: changeConfig, + child: const Text('Change config'), + ), + TextButton( + onPressed: generateError, + child: const Text('Generate error'), + ), + ], + ); Future generateError() async { - Catcher.sendTestException(); + Catcher2.sendTestException(); } void changeConfig() { - catcher.updateConfig( - debugConfig: CatcherOptions( + catcher2.updateConfig( + debugConfig: Catcher2Options( PageReportMode(), [ConsoleHandler()], ), diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt index a039cde0..a7a346e6 100644 --- a/example/linux/CMakeLists.txt +++ b/example/linux/CMakeLists.txt @@ -4,10 +4,10 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "example") +set(BINARY_NAME "catcher_2_example") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.jhomlala.example") +set(APPLICATION_ID "com.jhomlala.catcher_2") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. @@ -86,6 +86,8 @@ set_target_properties(${BINARY_NAME} RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) +# Enable the test target. +set(include_catcher_2_tests TRUE) # Generated plugin build rules, which manage building the plugins and adding # them to the application. diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc index c0530d42..5bd1eaad 100644 --- a/example/linux/my_application.cc +++ b/example/linux/my_application.cc @@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_title(header_bar, "catcher_2_example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "example"); + gtk_window_set_title(window, "catcher_2_example"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..e67ffa04 --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,18 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import device_info_plus +import flutter_local_notifications +import package_info_plus +import path_provider_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 1be478ca..b067d51c 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -64,7 +64,7 @@ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* catcher_2_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "catcher_2_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -131,7 +131,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* catcher_2_example.app */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; @@ -217,7 +217,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* catcher_2_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -385,10 +385,10 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/catcher_2_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/catcher_2_example"; }; name = Debug; }; @@ -399,10 +399,10 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/catcher_2_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/catcher_2_example"; }; name = Release; }; @@ -413,10 +413,10 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/catcher_2_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/catcher_2_example"; }; name = Profile; }; diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15368ecc..b5754d2e 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -65,7 +65,7 @@ @@ -82,7 +82,7 @@ diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig index 4d8b0cf9..4346045d 100644 --- a/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = catcher_2_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.example +PRODUCT_BUNDLE_IDENTIFIER = com.jhomlala.catcher2Example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2024 com.jhomlala. All rights reserved. diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift index 61f3bd1f..129f3757 100644 --- a/example/macos/RunnerTests/RunnerTests.swift +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -2,11 +2,26 @@ import Cocoa import FlutterMacOS import XCTest +@testable import catcher_2 + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + class RunnerTests: XCTestCase { - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + func testGetPlatformVersion() { + let plugin = Catcher_2Plugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, + "macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5e81366b..98d7cc29 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,30 +1,93 @@ -name: example -publish_to: 'none' -version: 1.0.0+1 +name: catcher_2_example +description: "Demonstrates how to use the catcher_2 package." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + sdk: '>=3.4.4 <4.0.0' +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter + + catcher_2: + # When depending on this package from a real application you should use: + # catcher_2: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.6 + + dio: ^5.0.1 + flutter_local_notifications: ^17.0.0 flutter_localizations: sdk: flutter - path_provider: ^2.1.3 - dio: ^5.4.3+1 - permission_handler: ^11.3.1 - flutter_local_notifications: ^17.1.2 - sentry: ^8.2.0 - catcher: - path: ../ + path_provider: ^2.0.1 + permission_handler: ^11.0.0 + sentry: '>=7.2.0 <9.0.0' dev_dependencies: - very_good_analysis: ^5.1.0 - + integration_test: + sdk: flutter flutter_test: sdk: flutter -flutter: + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/web/index.html b/example/web/index.html index b1cfdbbe..377e068f 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -18,18 +18,18 @@ - + - + - example + catcher_2_example diff --git a/example/web/manifest.json b/example/web/manifest.json index 542c7efc..d26c61ab 100644 --- a/example/web/manifest.json +++ b/example/web/manifest.json @@ -1,11 +1,11 @@ { - "name": "example", - "short_name": "example", + "name": "catcher_2_example", + "short_name": "catcher_2_example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "A project to showcase Catcher.", + "description": "Demonstrates how to use the catcher_2 plugin.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt index d960948a..ecc4ade1 100644 --- a/example/windows/CMakeLists.txt +++ b/example/windows/CMakeLists.txt @@ -1,10 +1,10 @@ # Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(example LANGUAGES CXX) +project(catcher_2_example LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "example") +set(BINARY_NAME "catcher_2_example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. @@ -52,6 +52,8 @@ add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") +# Enable the test target. +set(include_catcher_2_tests TRUE) # Generated plugin build rules, which manage building the plugins and adding # them to the application. diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc index 4e7e4ad1..463b846f 100644 --- a/example/windows/runner/Runner.rc +++ b/example/windows/runner/Runner.rc @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.jhomlala" "\0" - VALUE "FileDescription", "example" "\0" + VALUE "FileDescription", "catcher_2_example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "example" "\0" + VALUE "InternalName", "catcher_2_example" "\0" VALUE "LegalCopyright", "Copyright (C) 2024 com.jhomlala. All rights reserved." "\0" - VALUE "OriginalFilename", "example.exe" "\0" - VALUE "ProductName", "example" "\0" + VALUE "OriginalFilename", "catcher_2_example.exe" "\0" + VALUE "ProductName", "catcher_2_example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp index a61bf80d..3f17d09c 100644 --- a/example/windows/runner/main.cpp +++ b/example/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.Create(L"example", origin, size)) { + if (!window.Create(L"catcher_2_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/lib/catcher.dart b/lib/catcher.dart deleted file mode 100644 index 342096aa..00000000 --- a/lib/catcher.dart +++ /dev/null @@ -1,25 +0,0 @@ -export 'package:catcher/core/catcher.dart'; -export 'package:catcher/core/catcher_screenshot.dart'; -export 'package:catcher/handlers/console_handler.dart'; -export 'package:catcher/handlers/discord_handler.dart'; -export 'package:catcher/handlers/email_auto_handler.dart'; -export 'package:catcher/handlers/email_manual_handler.dart'; -export 'package:catcher/handlers/file_handler.dart'; -export 'package:catcher/handlers/http_handler.dart'; -export 'package:catcher/handlers/sentry_handler.dart'; -export 'package:catcher/handlers/slack_handler.dart'; -export 'package:catcher/handlers/snackbar_handler.dart'; -export 'package:catcher/handlers/toast_handler.dart'; -export 'package:catcher/mode/dialog_report_mode.dart'; -export 'package:catcher/mode/page_report_mode.dart'; -export 'package:catcher/mode/report_mode_action_confirmed.dart'; -export 'package:catcher/mode/silent_report_mode.dart'; -export 'package:catcher/model/catcher_options.dart'; -export 'package:catcher/model/http_request_type.dart'; -export 'package:catcher/model/localization_options.dart'; -export 'package:catcher/model/report.dart'; -export 'package:catcher/model/report_handler.dart'; -export 'package:catcher/model/report_mode.dart'; -export 'package:catcher/model/toast_handler_gravity.dart'; -export 'package:catcher/model/toast_handler_length.dart'; -export 'package:catcher/utils/catcher_logger.dart'; diff --git a/lib/catcher_2.dart b/lib/catcher_2.dart new file mode 100644 index 00000000..dc20da03 --- /dev/null +++ b/lib/catcher_2.dart @@ -0,0 +1,25 @@ +export 'package:catcher_2/core/catcher_2.dart'; +export 'package:catcher_2/core/catcher_2_screenshot.dart'; +export 'package:catcher_2/handlers/console_handler.dart'; +export 'package:catcher_2/handlers/discord_handler.dart'; +export 'package:catcher_2/handlers/email_auto_handler.dart'; +export 'package:catcher_2/handlers/email_manual_handler.dart'; +export 'package:catcher_2/handlers/file_handler.dart'; +export 'package:catcher_2/handlers/http_handler.dart'; +export 'package:catcher_2/handlers/sentry_handler.dart'; +export 'package:catcher_2/handlers/slack_handler.dart'; +export 'package:catcher_2/handlers/snackbar_handler.dart'; +export 'package:catcher_2/handlers/toast_handler.dart'; +export 'package:catcher_2/mode/dialog_report_mode.dart'; +export 'package:catcher_2/mode/page_report_mode.dart'; +export 'package:catcher_2/mode/report_mode_action_confirmed.dart'; +export 'package:catcher_2/mode/silent_report_mode.dart'; +export 'package:catcher_2/model/catcher_2_options.dart'; +export 'package:catcher_2/model/http_request_type.dart'; +export 'package:catcher_2/model/localization_options.dart'; +export 'package:catcher_2/model/report.dart'; +export 'package:catcher_2/model/report_handler.dart'; +export 'package:catcher_2/model/report_mode.dart'; +export 'package:catcher_2/model/toast_handler_gravity.dart'; +export 'package:catcher_2/model/toast_handler_length.dart'; +export 'package:catcher_2/utils/catcher_2_logger.dart'; diff --git a/lib/core/application_profile_manager.dart b/lib/core/application_profile_manager.dart index 361c2de8..336027bb 100644 --- a/lib/core/application_profile_manager.dart +++ b/lib/core/application_profile_manager.dart @@ -1,4 +1,4 @@ -import 'package:catcher/model/application_profile.dart'; +import 'package:catcher_2/model/application_profile.dart'; import 'package:flutter/foundation.dart'; import 'package:universal_io/io.dart'; @@ -15,25 +15,25 @@ class ApplicationProfileManager { return ApplicationProfile.profile; } - ///Fallback + /// Fallback return ApplicationProfile.debug; } - /// Check if current platform is web + /// Check if current platform is Web static bool isWeb() => kIsWeb; - /// Check if current platform is android - static bool isAndroid() => Platform.isAndroid; + /// Check if current platform is Android + static bool isAndroid() => !kIsWeb && Platform.isAndroid; - /// Check if current platform is ios - static bool isIos() => Platform.isIOS; + /// Check if current platform is iOS + static bool isIos() => !kIsWeb && Platform.isIOS; - ///Check if current platform is linux - static bool isLinux() => Platform.isLinux; + /// Check if current platform is Linux + static bool isLinux() => !kIsWeb && Platform.isLinux; - ///Check if current platform is windows - static bool isWindows() => Platform.isWindows; + /// Check if current platform is Windows + static bool isWindows() => !kIsWeb && Platform.isWindows; - ///Check if current platform is macOS - static bool isMacOS() => Platform.isMacOS; + /// Check if current platform is macOS + static bool isMacOS() => !kIsWeb && Platform.isMacOS; } diff --git a/lib/core/catcher.dart b/lib/core/catcher_2.dart similarity index 65% rename from lib/core/catcher.dart rename to lib/core/catcher_2.dart index 00bd31e5..54939fbc 100644 --- a/lib/core/catcher.dart +++ b/lib/core/catcher_2.dart @@ -1,52 +1,73 @@ import 'dart:async'; -import 'dart:io'; import 'dart:isolate'; -import 'package:catcher/core/application_profile_manager.dart'; -import 'package:catcher/core/catcher_screenshot_manager.dart'; -import 'package:catcher/mode/report_mode_action_confirmed.dart'; -import 'package:catcher/model/application_profile.dart'; -import 'package:catcher/model/catcher_options.dart'; -import 'package:catcher/model/localization_options.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/model/report_mode.dart'; -import 'package:catcher/utils/catcher_error_widget.dart'; -import 'package:catcher/utils/catcher_logger.dart'; +import 'package:catcher_2/core/application_profile_manager.dart'; +import 'package:catcher_2/core/catcher_2_screenshot_manager.dart'; +import 'package:catcher_2/mode/report_mode_action_confirmed.dart'; +import 'package:catcher_2/model/application_profile.dart'; +import 'package:catcher_2/model/catcher_2_options.dart'; +import 'package:catcher_2/model/localization_options.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/model/report_mode.dart'; +import 'package:catcher_2/utils/catcher_2_error_widget.dart'; +import 'package:catcher_2/utils/catcher_2_logger.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -class Catcher extends ReportModeAction { - static late Catcher _instance; +class Catcher2 implements ReportModeAction { + /// Builds catcher 2 instance + Catcher2({ + this.rootWidget, + this.runAppFunction, + this.releaseConfig, + this.debugConfig, + this.profileConfig, + this.enableLogger = true, + this.ensureInitialized = false, + GlobalKey? navigatorKey, + }) : assert( + rootWidget != null || runAppFunction != null, + 'You need to provide rootWidget or runAppFunction', + ) { + _configure(navigatorKey); + } + + static late Catcher2 _instance; static GlobalKey? _navigatorKey; - /// Root widget which will be ran + /// Root widget that is run using [runApp], see also [runAppFunction] if you + /// want to customise how the widget is run final Widget? rootWidget; - ///Run app function which will be ran + /// The function to be executed after setup, should at least call [runApp]. + /// See also [rootWidget] if no special configuration is necessary and only a + /// call to [runApp] is enough. final void Function()? runAppFunction; - /// Instance of catcher config used in release mode - CatcherOptions? releaseConfig; + /// Instance of catcher 2 config used in release mode + Catcher2Options? releaseConfig; - /// Instance of catcher config used in debug mode - CatcherOptions? debugConfig; + /// Instance of catcher 2 config used in debug mode + Catcher2Options? debugConfig; - /// Instance of catcher config used in profile mode - CatcherOptions? profileConfig; + /// Instance of catcher 2 config used in profile mode + Catcher2Options? profileConfig; - /// Should catcher logs be enabled + /// Should catcher 2 logs be enabled final bool enableLogger; - /// Should catcher run WidgetsFlutterBinding.ensureInitialized() during + /// Should catcher 2 run WidgetsFlutterBinding.ensureInitialized() during /// initialization. final bool ensureInitialized; - late CatcherOptions _currentConfig; - late CatcherLogger _logger; - late CatcherScreenshotManager screenshotManager; + late Catcher2Options _currentConfig; + late Catcher2Logger _logger; + late Catcher2ScreenshotManager screenshotManager; final Map _deviceParameters = {}; final Map _applicationParameters = {}; final List _cachedReports = []; @@ -54,36 +75,23 @@ class Catcher extends ReportModeAction { LocalizationOptions? _localizationOptions; /// Instance of navigator key - static GlobalKey? get navigatorKey { - return _navigatorKey; - } - - /// Builds catcher instance - Catcher({ - this.rootWidget, - this.runAppFunction, - this.releaseConfig, - this.debugConfig, - this.profileConfig, - this.enableLogger = true, - this.ensureInitialized = false, - GlobalKey? navigatorKey, - }) : assert( - rootWidget != null || runAppFunction != null, - 'You need to provide rootWidget or runAppFunction', - ) { - _configure(navigatorKey); - } + static GlobalKey? get navigatorKey => _navigatorKey; void _configure(GlobalKey? navigatorKey) { _instance = this; _configureNavigatorKey(navigatorKey); _setupCurrentConfig(); + _setupLogger(); _configureLogger(); - _setupErrorHooks(); _setupReportModeActionInReportMode(); _setupScreenshotManager(); + _setupErrorHooks(); + + _initWidgetsBindingAndRunApp(); + + // Loading device and application info requires that the widgets binding is + // initialized so we need to run it after we init WidgetsFlutterBinding. _loadDeviceInfo(); _loadApplicationInfo(); @@ -93,7 +101,7 @@ class Catcher extends ReportModeAction { 'process error reports.', ); } else { - _logger.fine('Catcher configured successfully.'); + _logger.fine('Catcher 2 configured successfully.'); } } @@ -108,40 +116,22 @@ class Catcher extends ReportModeAction { void _setupCurrentConfig() { switch (ApplicationProfileManager.getApplicationProfile()) { case ApplicationProfile.release: - { - if (releaseConfig != null) { - _currentConfig = releaseConfig!; - } else { - _currentConfig = CatcherOptions.getDefaultReleaseOptions(); - } - break; - } + _currentConfig = + releaseConfig ?? Catcher2Options.getDefaultReleaseOptions(); case ApplicationProfile.debug: - { - if (debugConfig != null) { - _currentConfig = debugConfig!; - } else { - _currentConfig = CatcherOptions.getDefaultDebugOptions(); - } - break; - } + _currentConfig = + debugConfig ?? Catcher2Options.getDefaultDebugOptions(); case ApplicationProfile.profile: - { - if (profileConfig != null) { - _currentConfig = profileConfig!; - } else { - _currentConfig = CatcherOptions.getDefaultProfileOptions(); - } - break; - } + _currentConfig = + profileConfig ?? Catcher2Options.getDefaultProfileOptions(); } } - ///Update config after initialization + /// Update config after initialization void updateConfig({ - CatcherOptions? debugConfig, - CatcherOptions? profileConfig, - CatcherOptions? releaseConfig, + Catcher2Options? debugConfig, + Catcher2Options? profileConfig, + Catcher2Options? releaseConfig, }) { if (debugConfig != null) { this.debugConfig = debugConfig; @@ -153,49 +143,60 @@ class Catcher extends ReportModeAction { this.releaseConfig = releaseConfig; } _setupCurrentConfig(); + _configureLogger(); _setupReportModeActionInReportMode(); _setupScreenshotManager(); - _configureLogger(); _localizationOptions = null; } void _setupReportModeActionInReportMode() { - _currentConfig.reportMode.setReportModeAction(this); + _currentConfig.reportMode.reportModeAction = this; _currentConfig.explicitExceptionReportModesMap.forEach( (error, reportMode) { - reportMode.setReportModeAction(this); + reportMode.reportModeAction = this; }, ); } void _setupLocalizationsOptionsInReportMode() { - _currentConfig.reportMode.setLocalizationOptions(_localizationOptions); + _currentConfig.reportMode.localizationOptions = _localizationOptions; _currentConfig.explicitExceptionReportModesMap.forEach( (error, reportMode) { - reportMode.setLocalizationOptions(_localizationOptions); + reportMode.localizationOptions = _localizationOptions; }, ); } void _setupLocalizationsOptionsInReportsHandler() { - _currentConfig.handlers.forEach((handler) { - handler.setLocalizationOptions(_localizationOptions); - }); + for (final handler in _currentConfig.handlers) { + handler.localizationOptions = _localizationOptions; + } } Future _setupErrorHooks() async { - FlutterError.onError = (FlutterErrorDetails details) async { + // FlutterError.onError catches SYNCHRONOUS errors for all platforms + FlutterError.onError = (details) async { await _reportError( details.exception, details.stack, errorDetails: details, ); + _currentConfig.onFlutterError?.call(details); + }; + + // PlatformDispatcher.instance.onError catches ASYNCHRONOUS errors, but it + // currently does not work for Web, most likely due to this issue: + // https://github.com/flutter/flutter/issues/100277 + PlatformDispatcher.instance.onError = (error, stack) { + _reportError(error, stack); + _currentConfig.onPlatformError?.call(error, stack); + return true; }; - ///Web doesn't have Isolate error listener support - if (!ApplicationProfileManager.isWeb()) { + // Web doesn't have Isolate error listener support + if (!kIsWeb) { Isolate.current.addErrorListener( - RawReceivePort((dynamic pair) async { + RawReceivePort((pair) async { final isolateError = pair as List; await _reportError( isolateError.first.toString(), @@ -204,88 +205,97 @@ class Catcher extends ReportModeAction { }).sendPort, ); } + } + + void _initWidgetsBindingAndRunApp() { + if (kIsWeb) { + // Due to https://github.com/flutter/flutter/issues/100277 + // this is still needed... As soon as proper error catching support + // for Web is implemented, this branch should be merged with the other. + unawaited( + runZonedGuarded>( + () async { + // It is important that we run init widgets binding inside the + // runZonedGuarded call to be able to catch the async exceptions. + _initWidgetsBinding(); + _runApp(); + }, + (error, stack) { + _reportError(error, stack); + _currentConfig.onPlatformError?.call(error, stack); + }, + ), + ); + } else { + // This isn't Web, we can just run the app, no need for runZoneGuarded + // since async errors are caught by PlatformDispatcher.instance.onError. + _initWidgetsBinding(); + _runApp(); + } + } + void _runApp() { if (rootWidget != null) { - _runZonedGuarded(() { - runApp(rootWidget!); - }); + runApp(rootWidget!); } else if (runAppFunction != null) { - _runZonedGuarded(() { - runAppFunction!(); - }); + runAppFunction!(); } else { - throw ArgumentError('Provide rootWidget or runAppFunction to Catcher.'); + throw ArgumentError('Provide rootWidget or runAppFunction to Catcher 2.'); } } - void _runZonedGuarded(void Function() callback) { - runZonedGuarded>( - () async { - if (ensureInitialized) { - WidgetsFlutterBinding.ensureInitialized(); - } - callback(); - }, - _reportError, - ); + void _initWidgetsBinding() { + if (ensureInitialized) { + WidgetsFlutterBinding.ensureInitialized(); + } } - void _configureLogger() { - if (_currentConfig.logger != null) { - _logger = _currentConfig.logger!; - } else { - _logger = CatcherLogger(); - } + void _setupLogger() { + _logger = _currentConfig.logger ?? Catcher2Logger(); if (enableLogger) { _logger.setup(); } + } - _currentConfig.handlers.forEach((handler) { + void _configureLogger() { + for (final handler in _currentConfig.handlers) { handler.logger = _logger; - }); + } } - void _loadDeviceInfo() { - final deviceInfo = DeviceInfoPlugin(); - if (ApplicationProfileManager.isWeb()) { - deviceInfo.webBrowserInfo.then((webBrowserInfo) { + Future _loadDeviceInfo() async { + try { + final deviceInfo = DeviceInfoPlugin(); + if (ApplicationProfileManager.isWeb()) { + final webBrowserInfo = await deviceInfo.webBrowserInfo; _loadWebParameters(webBrowserInfo); - _removeExcludedParameters(); - }); - } else if (ApplicationProfileManager.isLinux()) { - deviceInfo.linuxInfo.then((linuxDeviceInfo) { + } else if (ApplicationProfileManager.isLinux()) { + final linuxDeviceInfo = await deviceInfo.linuxInfo; _loadLinuxParameters(linuxDeviceInfo); - _removeExcludedParameters(); - }); - } else if (ApplicationProfileManager.isWindows()) { - deviceInfo.windowsInfo.then((windowsInfo) { + } else if (ApplicationProfileManager.isWindows()) { + final windowsInfo = await deviceInfo.windowsInfo; _loadWindowsParameters(windowsInfo); - _removeExcludedParameters(); - }); - } else if (ApplicationProfileManager.isMacOS()) { - deviceInfo.macOsInfo.then((macOsDeviceInfo) { + } else if (ApplicationProfileManager.isMacOS()) { + final macOsDeviceInfo = await deviceInfo.macOsInfo; _loadMacOSParameters(macOsDeviceInfo); - _removeExcludedParameters(); - }); - } else if (ApplicationProfileManager.isAndroid()) { - deviceInfo.androidInfo.then((androidInfo) { + } else if (ApplicationProfileManager.isAndroid()) { + final androidInfo = await deviceInfo.androidInfo; _loadAndroidParameters(androidInfo); - _removeExcludedParameters(); - }); - } else if (ApplicationProfileManager.isIos()) { - deviceInfo.iosInfo.then((iosInfo) { + } else if (ApplicationProfileManager.isIos()) { + final iosInfo = await deviceInfo.iosInfo; _loadIosParameters(iosInfo); - _removeExcludedParameters(); - }); - } else { - _logger.info("Couldn't load device info for unsupported device type."); + } else { + _logger.info("Couldn't load device info for unsupported device type."); + } + _removeExcludedParameters(); + } catch (exception) { + _logger.warning("Couldn't load device info due to error: $exception"); } } - ///Remove excluded parameters from device parameters. - void _removeExcludedParameters() { - _currentConfig.excludedParameters.forEach(_deviceParameters.remove); - } + /// Remove excluded parameters from device parameters. + void _removeExcludedParameters() => + _currentConfig.excludedParameters.forEach(_deviceParameters.remove); void _loadLinuxParameters(LinuxDeviceInfo linuxDeviceInfo) { try { @@ -332,7 +342,7 @@ class Catcher extends ReportModeAction { } } - Future _loadWebParameters(WebBrowserInfo webBrowserInfo) async { + void _loadWebParameters(WebBrowserInfo webBrowserInfo) { try { _deviceParameters['language'] = webBrowserInfo.language; _deviceParameters['appCodeName'] = webBrowserInfo.appCodeName; @@ -358,7 +368,7 @@ class Catcher extends ReportModeAction { void _loadAndroidParameters(AndroidDeviceInfo androidDeviceInfo) { try { _deviceParameters['id'] = androidDeviceInfo.id; - // TODO(*): _deviceParameters['androidId'] = androidDeviceInfo.androidId; + // TODO(N): _deviceParameters["androidId"] = androidDeviceInfo.androidId; _deviceParameters['board'] = androidDeviceInfo.board; _deviceParameters['bootloader'] = androidDeviceInfo.bootloader; _deviceParameters['brand'] = androidDeviceInfo.brand; @@ -407,20 +417,24 @@ class Catcher extends ReportModeAction { } } - void _loadApplicationInfo() { - _applicationParameters['environment'] = - ApplicationProfileManager.getApplicationProfile().name; + Future _loadApplicationInfo() async { + try { + _applicationParameters['environment'] = + ApplicationProfileManager.getApplicationProfile().name; - PackageInfo.fromPlatform().then((packageInfo) { + final packageInfo = await PackageInfo.fromPlatform(); _applicationParameters['version'] = packageInfo.version; _applicationParameters['appName'] = packageInfo.appName; _applicationParameters['buildNumber'] = packageInfo.buildNumber; _applicationParameters['packageName'] = packageInfo.packageName; - }); + } catch (exception) { + _logger + .warning("Couldn't load application info due to error: $exception"); + } } - ///We need to setup localizations lazily because context needed to setup these - ///localizations can be used after app was build for the first time. + /// We need to setup localizations lazily because context needed to setup + /// these localizations can be used after app was build for the first time. void _setupLocalization() { var locale = const Locale('en', 'US'); if (_isContextValid()) { @@ -428,7 +442,7 @@ class Catcher extends ReportModeAction { if (context != null) { locale = Localizations.localeOf(context); } - if (_currentConfig.localizationOptions.isNotEmpty == true) { + if (_currentConfig.localizationOptions.isNotEmpty) { for (final options in _currentConfig.localizationOptions) { if (options.languageCode.toLowerCase() == locale.languageCode.toLowerCase()) { @@ -448,8 +462,8 @@ class Catcher extends ReportModeAction { String language, ) { switch (language.toLowerCase()) { - case 'en': - return LocalizationOptions.buildDefaultEnglishOptions(); + case 'ar': + return LocalizationOptions.buildDefaultArabicOptions(); case 'zh': return LocalizationOptions.buildDefaultChineseOptions(); case 'hi': @@ -474,38 +488,46 @@ class Catcher extends ReportModeAction { return LocalizationOptions.buildDefaultDutchOptions(); case 'de': return LocalizationOptions.buildDefaultGermanOptions(); - default: + case 'tr': + return LocalizationOptions.buildDefaultTurkishOptions(); + default: // Also covers 'en' return LocalizationOptions.buildDefaultEnglishOptions(); } } - ///Setup screenshot manager's screenshots path. + /// Setup screenshot manager's screenshots path. void _setupScreenshotManager() { - screenshotManager = CatcherScreenshotManager(_logger); + screenshotManager = Catcher2ScreenshotManager(_logger); final screenshotsPath = _currentConfig.screenshotsPath; if (!ApplicationProfileManager.isWeb() && screenshotsPath.isEmpty) { - _logger.warning("Screenshots path is empty. Screenshots won't work."); + _logger.warning( + "Screenshots path is empty. Screenshots won't be saved locally.", + ); } screenshotManager.path = screenshotsPath; } - /// Report checked error (error caught in try-catch block). Catcher will treat - /// this as normal exception and pass it to handlers. - static void reportCheckedError(dynamic error, dynamic stackTrace) { + /// Report checked error (error caught in try-catch block). Catcher 2 will + /// treat this as normal exception and pass it to handlers. + static void reportCheckedError( + error, + stackTrace, { + Map? extraData, + }) { dynamic errorValue = error; dynamic stackTraceValue = stackTrace; errorValue ??= 'undefined error'; stackTraceValue ??= StackTrace.current; - _instance._reportError(error, stackTrace); + _instance._reportError(errorValue, stackTraceValue, extraData: extraData); } Future _reportError( - dynamic error, - dynamic stackTrace, { + error, + stackTrace, { FlutterErrorDetails? errorDetails, + Map? extraData, }) async { - if (errorDetails?.silent ?? - false == true && _currentConfig.handleSilentError == false) { + if ((errorDetails?.silent ?? false) && !_currentConfig.handleSilentError) { _logger.info( 'Report error skipped for error: $error. HandleSilentError is false.', ); @@ -519,9 +541,11 @@ class Catcher extends ReportModeAction { _cleanPastReportsOccurrences(); - File? screenshot; - if (!ApplicationProfileManager.isWeb()) { + XFile? screenshot; + try { screenshot = await screenshotManager.captureAndSave(); + } catch (e) { + _logger.warning('Failed to create screenshot file: $e'); } final report = Report( @@ -530,7 +554,7 @@ class Catcher extends ReportModeAction { DateTime.now(), _deviceParameters, _applicationParameters, - _currentConfig.customParameters, + {..._currentConfig.customParameters, ...(extraData ?? {})}, errorDetails, _getPlatformType(), screenshot, @@ -545,10 +569,10 @@ class Catcher extends ReportModeAction { } if (_currentConfig.filterFunction != null && - _currentConfig.filterFunction!(report) == false) { + !_currentConfig.filterFunction!(report)) { _logger.fine( - "Error: '$error' has been filtered from Catcher logs. Report will be " - 'skipped.', + "Error: '$error' has been filtered from Catcher 2 logs. " + 'Report will be skipped.', ); return; } @@ -561,21 +585,20 @@ class Catcher extends ReportModeAction { } if (!isReportModeSupportedInPlatform(report, reportMode)) { _logger.warning( - '$reportMode in not supported for ${report.platformType.name}' - 'platform', + '$reportMode is not supported for ${report.platformType.name} platform', ); return; } - _addReportInReportsOccurencesMap(report); + _addReportInReportsOccurrencesMap(report); if (reportMode.isContextRequired()) { if (_isContextValid()) { reportMode.requestAction(report, _getContext()); } else { _logger.warning( - "Couldn't use report mode because you didn't provide navigator key." - ' Add navigator key to use this report mode.', + "Couldn't use report mode because you didn't provide navigator key. " + 'Add navigator key to use this report mode.', ); } } else { @@ -585,14 +608,10 @@ class Catcher extends ReportModeAction { /// Check if given report mode is enabled in current platform. Only supported /// handlers in given report mode can be used. - bool isReportModeSupportedInPlatform(Report report, ReportMode reportMode) { - if (reportMode.getSupportedPlatforms().isEmpty) { - return false; - } - return reportMode.getSupportedPlatforms().contains(report.platformType); - } + bool isReportModeSupportedInPlatform(Report report, ReportMode reportMode) => + reportMode.getSupportedPlatforms().contains(report.platformType); - ReportMode? _getReportModeFromExplicitExceptionReportModeMap(dynamic error) { + ReportMode? _getReportModeFromExplicitExceptionReportModeMap(error) { final errorName = error != null ? error.toString().toLowerCase() : ''; ReportMode? reportMode; _currentConfig.explicitExceptionReportModesMap.forEach((key, value) { @@ -605,7 +624,7 @@ class Catcher extends ReportModeAction { } ReportHandler? _getReportHandlerFromExplicitExceptionHandlerMap( - dynamic error, + error, ) { final errorName = error != null ? error.toString().toLowerCase() : ''; ReportHandler? reportHandler; @@ -635,40 +654,34 @@ class Catcher extends ReportModeAction { void _handleReport(Report report, ReportHandler reportHandler) { if (!isReportHandlerSupportedInPlatform(report, reportHandler)) { - _logger.warning( - '$reportHandler in not supported for ' - '${report.platformType.name} platform', - ); + _logger.warning('$reportHandler in not supported for ' + '${report.platformType.name} platform'); return; } if (reportHandler.isContextRequired() && !_isContextValid()) { _logger.warning( - "Couldn't use report handler because you didn't provide navigator key." - ' Add navigator key to use this report mode', + "Couldn't use report handler because you didn't provide navigator key. " + 'Add navigator key to use this report mode.', ); return; } - reportHandler - .handle(report, _getContext()) - .catchError((dynamic handlerError) { - _logger.warning( - 'Error occurred in $reportHandler: $handlerError', - ); - return true; + reportHandler.handle(report, _getContext()).catchError((handlerError) { + _logger.warning('Error occurred in $reportHandler: $handlerError'); + return true; // Shut up warnings }).then((result) { - _logger.info('${report.runtimeType} result: $result'); - if (!result) { - _logger.warning('$reportHandler failed to report error'); - } else { + if (result) { + _logger.info('$reportHandler successfully reported an error'); _cachedReports.remove(report); + } else { + _logger.warning('$reportHandler failed to report an error'); } }).timeout( Duration(milliseconds: _currentConfig.handlerTimeout), onTimeout: () { _logger.warning( - '$reportHandler failed to report error because of timeout', + '$reportHandler failed to report an error because of timeout', ); }, ); @@ -680,7 +693,7 @@ class Catcher extends ReportModeAction { Report report, ReportHandler reportHandler, ) { - if (reportHandler.getSupportedPlatforms().isEmpty == true) { + if (reportHandler.getSupportedPlatforms().isEmpty) { return false; } return reportHandler.getSupportedPlatforms().contains(report.platformType); @@ -697,22 +710,16 @@ class Catcher extends ReportModeAction { _cachedReports.remove(report); } - BuildContext? _getContext() { - return navigatorKey?.currentState?.overlay?.context; - } + BuildContext? _getContext() => navigatorKey?.currentState?.overlay?.context; - bool _isContextValid() { - return navigatorKey?.currentState?.overlay != null; - } + bool _isContextValid() => navigatorKey?.currentState?.overlay != null; /// Get currently used config. - CatcherOptions? getCurrentConfig() { - return _currentConfig; - } + Catcher2Options? getCurrentConfig() => _currentConfig; - /// Send text exception. Used to test Catcher configuration. + /// Send text exception. Used to test Catcher 2 configuration. static void sendTestException() { - throw const FormatException('Test exception generated by Catcher'); + throw const FormatException('Test exception generated by Catcher 2'); } /// Add default error widget which replaces red screen of death (RSOD). @@ -724,18 +731,16 @@ class Catcher extends ReportModeAction { 'able to recover from error state.', double maxWidthForSmallMode = 150, }) { - ErrorWidget.builder = (FlutterErrorDetails details) { - return CatcherErrorWidget( - details: details, - showStacktrace: showStacktrace, - title: title, - description: description, - maxWidthForSmallMode: maxWidthForSmallMode, - ); - }; + ErrorWidget.builder = (details) => Catcher2ErrorWidget( + details: details, + showStacktrace: showStacktrace, + title: title, + description: description, + maxWidthForSmallMode: maxWidthForSmallMode, + ); } - ///Get platform type based on device. + /// Get platform type based on device. PlatformType _getPlatformType() { if (ApplicationProfileManager.isWeb()) { return PlatformType.web; @@ -759,7 +764,7 @@ class Catcher extends ReportModeAction { return PlatformType.unknown; } - ///Clean report occurrencess from the past. + /// Clean report occurrences from the past. void _cleanPastReportsOccurrences() { final occurrenceTimeout = _currentConfig.reportOccurrenceTimeout; final nowDateTime = DateTime.now(); @@ -770,7 +775,7 @@ class Catcher extends ReportModeAction { }); } - ///Check whether reports occurence map contains given report. + /// Check whether reports occurrence map contains given report. bool _isReportInReportsOccurrencesMap(Report report) { if (report.error != null) { return _reportsOccurrenceMap.containsValue(report.error.toString()); @@ -779,16 +784,14 @@ class Catcher extends ReportModeAction { } } - ///Add report in reports occurences map. Report will be added only when - ///error is not null and report occurence timeout is greater than 0. - void _addReportInReportsOccurencesMap(Report report) { + /// Add report in reports occurrences map. Report will be added only when + /// error is not null and report occurrence timeout is greater than 0. + void _addReportInReportsOccurrencesMap(Report report) { if (report.error != null && _currentConfig.reportOccurrenceTimeout > 0) { _reportsOccurrenceMap[DateTime.now()] = report.error.toString(); } } - ///Get current Catcher instance. - static Catcher getInstance() { - return _instance; - } + /// Get current Catcher 2 instance. + static Catcher2 getInstance() => _instance; } diff --git a/lib/core/catcher_2_screenshot.dart b/lib/core/catcher_2_screenshot.dart new file mode 100644 index 00000000..a4e0ef22 --- /dev/null +++ b/lib/core/catcher_2_screenshot.dart @@ -0,0 +1,27 @@ +import 'package:catcher_2/core/catcher_2.dart'; +import 'package:flutter/material.dart'; + +/// Screenshot widget used to create screenshot of all child widgets. +class Catcher2Screenshot extends StatefulWidget { + const Catcher2Screenshot({ + super.key, + required this.child, + required this.catcher2, + }); + + final Widget child; + final Catcher2 catcher2; + + @override + State createState() => Catcher2ScreenshotState(); +} + +/// State of screenshot widget. +class Catcher2ScreenshotState extends State + with TickerProviderStateMixin { + @override + Widget build(BuildContext context) => RepaintBoundary( + key: widget.catcher2.screenshotManager.containerKey, + child: widget.child, + ); +} diff --git a/lib/core/catcher_2_screenshot_manager.dart b/lib/core/catcher_2_screenshot_manager.dart new file mode 100644 index 00000000..5cb9d763 --- /dev/null +++ b/lib/core/catcher_2_screenshot_manager.dart @@ -0,0 +1,106 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:catcher_2/utils/catcher_2_logger.dart'; +import 'package:cross_file/cross_file.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +/// Manager which takes screenshot of configured widget. Screenshot will be +/// saved to file which can be reused later. +class Catcher2ScreenshotManager { + Catcher2ScreenshotManager(this._logger) : _containerKey = GlobalKey(); + final Catcher2Logger _logger; + final GlobalKey _containerKey; + String? _path; + + /// Unique global key used to create screenshot + GlobalKey get containerKey => _containerKey; + + /// Create screenshot and save it in file. File will be created in directory + /// specified in `Catcher2Options`. + Future captureAndSave({ + double? pixelRatio, + Duration delay = const Duration(milliseconds: 20), + }) async { + try { + final content = await _capture( + pixelRatio: pixelRatio, + delay: delay, + ); + + if (content != null) { + return saveFile(content); + } + } catch (exception) { + _logger.warning('Failed to create screenshot file: $exception'); + } + return null; + } + + Future saveFile(Uint8List fileContent) async { + final name = 'catcher_2_${DateTime.now().microsecondsSinceEpoch}.png'; + final path = (_path?.isEmpty ?? true) ? name : '$_path/$name'; + final file = XFile.fromData(fileContent, path: path, name: name); + if (_path != null && _path!.isNotEmpty) { + await file.saveTo(path); + } + return file; + } + + Future _capture({ + double? pixelRatio, + Duration delay = const Duration(milliseconds: 20), + }) => + //Delay is required. See Issue https://github.com/flutter/flutter/issues/22308 + Future.delayed(delay, () async { + try { + final image = await captureAsUiImage( + delay: Duration.zero, + pixelRatio: pixelRatio, + ); + final byteData = + await image?.toByteData(format: ui.ImageByteFormat.png); + image?.dispose(); + return byteData?.buffer.asUint8List(); + } catch (exception) { + _logger.severe('Failed to capture screenshot: $exception'); + } + return null; + }); + + Future captureAsUiImage({ + double? pixelRatio, + Duration delay = const Duration(milliseconds: 20), + }) => + //Delay is required. See Issue https://github.com/flutter/flutter/issues/22308 + Future.delayed(delay, () async { + try { + final findRenderObject = + _containerKey.currentContext?.findRenderObject(); + + if (findRenderObject == null) { + return null; + } + + final boundary = findRenderObject as RenderRepaintBoundary; + final context = _containerKey.currentContext; + var pixelRatioValue = pixelRatio; + if (pixelRatioValue == null && context != null && context.mounted) { + pixelRatioValue = MediaQuery.of(context).devicePixelRatio; + } + return await boundary.toImage(pixelRatio: pixelRatioValue ?? 1); + } catch (exception) { + _logger.severe('Failed to capture screenshot: $exception'); + } + return null; + }); + + /// Update screenshots directory path. + // ignore: avoid_setters_without_getters + set path(String path) { + _path = path; + } +} diff --git a/lib/core/catcher_screenshot.dart b/lib/core/catcher_screenshot.dart deleted file mode 100644 index fcb54489..00000000 --- a/lib/core/catcher_screenshot.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:catcher/core/catcher.dart'; -import 'package:flutter/material.dart'; - -///Screenshot widget used to create screenshot of all child widgets. -class CatcherScreenshot extends StatefulWidget { - final Widget child; - final Catcher catcher; - - const CatcherScreenshot({ - required this.child, - required this.catcher, - super.key, - }); - - @override - State createState() { - return CatcherScreenshotState(); - } -} - -///State of screenshot widget. -class CatcherScreenshotState extends State - with TickerProviderStateMixin { - @override - Widget build(BuildContext context) { - return RepaintBoundary( - key: widget.catcher.screenshotManager.containerKey, - child: widget.child, - ); - } -} diff --git a/lib/core/catcher_screenshot_manager.dart b/lib/core/catcher_screenshot_manager.dart deleted file mode 100644 index 4539f3f8..00000000 --- a/lib/core/catcher_screenshot_manager.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:typed_data'; -import 'dart:ui' as ui; - -import 'package:catcher/utils/catcher_logger.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; - -///Manager which takes screenshot of configured widget. Screenshot will be saved -///to file which can be reused later. -class CatcherScreenshotManager { - final CatcherLogger _logger; - late GlobalKey _containerKey; - String? _path; - - CatcherScreenshotManager(this._logger) { - _containerKey = GlobalKey(); - } - - ///Unique global key used to create screenshot - GlobalKey get containerKey => _containerKey; - - ///Create screenshot and save it in file. File will be created in directory - ///specified in CatcherOptions. - Future captureAndSave({ - double? pixelRatio, - Duration delay = const Duration(milliseconds: 20), - }) async { - try { - if (_path?.isEmpty ?? false == true) { - return null; - } - final content = await _capture( - pixelRatio: pixelRatio, - delay: delay, - ); - - if (content != null) { - return saveFile(content); - } - } catch (exception) { - _logger.warning('Failed to create screenshot file: $exception'); - } - return null; - } - - Future saveFile(Uint8List fileContent) async { - final name = 'catcher_${DateTime.now().microsecondsSinceEpoch}.png'; - final file = await File('$_path/$name').create(recursive: true); - file.writeAsBytesSync(fileContent); - return file; - } - - Future _capture({ - double? pixelRatio, - Duration delay = const Duration(milliseconds: 20), - }) { - //Delay is required. See Issue https://github.com/flutter/flutter/issues/22308 - return Future.delayed(delay, () async { - try { - final image = await captureAsUiImage( - delay: Duration.zero, - pixelRatio: pixelRatio, - ); - final byteData = - await image?.toByteData(format: ui.ImageByteFormat.png); - image?.dispose(); - - final pngBytes = byteData?.buffer.asUint8List(); - - return pngBytes; - } catch (exception) { - _logger.severe('Failed to capture screenshot: $exception'); - } - return null; - }); - } - - Future captureAsUiImage({ - double? pixelRatio = 1, - Duration delay = const Duration(milliseconds: 20), - }) { - //Delay is required. See Issue https://github.com/flutter/flutter/issues/22308 - return Future.delayed(delay, () async { - try { - final findRenderObject = - _containerKey.currentContext?.findRenderObject(); - - print(containerKey.currentContext); - print(_containerKey.currentContext?.findRenderObject()); - if (findRenderObject == null) { - return null; - } - - final boundary = findRenderObject as RenderRepaintBoundary; - final context = _containerKey.currentContext; - var pixelRatioValue = pixelRatio; - if (pixelRatio == null) { - if (context != null) { - pixelRatioValue = - pixelRatio ?? MediaQuery.of(context).devicePixelRatio; - } - } - final image = await boundary.toImage(pixelRatio: pixelRatioValue ?? 1); - return image; - } catch (exception) { - _logger.severe('Failed to capture screenshot: $exception'); - } - return null; - }); - } - - ///Update screenshots directory path. - // ignore: avoid_setters_without_getters - set path(String path) { - _path = path; - } -} diff --git a/lib/handlers/base_email_handler.dart b/lib/handlers/base_email_handler.dart index 82f9cf35..f7b4e145 100644 --- a/lib/handlers/base_email_handler.dart +++ b/lib/handlers/base_email_handler.dart @@ -1,10 +1,19 @@ import 'dart:convert'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; -///Base class for all email handlers. +/// Base class for all email handlers. abstract class BaseEmailHandler extends ReportHandler { + BaseEmailHandler({ + required this.enableDeviceParameters, + required this.enableApplicationParameters, + required this.enableStackTrace, + required this.enableCustomParameters, + this.emailTitle, + this.emailHeader, + }); + final bool enableDeviceParameters; final bool enableApplicationParameters; final bool enableStackTrace; @@ -13,28 +22,19 @@ abstract class BaseEmailHandler extends ReportHandler { final String? emailHeader; final HtmlEscape _htmlEscape = const HtmlEscape(); - BaseEmailHandler( - this.enableDeviceParameters, - this.enableApplicationParameters, - this.enableStackTrace, - this.enableCustomParameters, - this.emailTitle, - this.emailHeader, - ); - - ///Setup email title from [report]. + /// Setup email title from [report]. String getEmailTitle(Report report) { - if (emailTitle?.isNotEmpty ?? false == true) { + if (emailTitle?.isNotEmpty ?? false) { return emailTitle!; } else { return 'Error report: >> ${report.error} <<'; } } - ///Setup html email message from [report]. + /// Setup html email message from [report]. String setupHtmlMessageText(Report report) { final buffer = StringBuffer(); - if (emailHeader?.isNotEmpty == true) { + if (emailHeader?.isNotEmpty ?? false) { buffer ..write(_escapeHtmlValue(emailHeader ?? '')) ..write('

'); @@ -83,15 +83,13 @@ abstract class BaseEmailHandler extends ReportHandler { return buffer.toString(); } - ///Escape html value from [value]. - String _escapeHtmlValue(dynamic value) { - return _htmlEscape.convert(value.toString()); - } + /// Escape html value from [value]. + String _escapeHtmlValue(value) => _htmlEscape.convert(value.toString()); - ///Setup raw text email message from [report]. + /// Setup raw text email message from [report]. String setupRawMessageText(Report report) { final buffer = StringBuffer(); - if (emailHeader?.isNotEmpty == true) { + if (emailHeader?.isNotEmpty ?? false) { buffer ..write(emailHeader) ..write('\n\n'); diff --git a/lib/handlers/console_handler.dart b/lib/handlers/console_handler.dart index 88b1df99..b3af604f 100644 --- a/lib/handlers/console_handler.dart +++ b/lib/handlers/console_handler.dart @@ -1,15 +1,9 @@ -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; import 'package:flutter/material.dart'; class ConsoleHandler extends ReportHandler { - final bool enableDeviceParameters; - final bool enableApplicationParameters; - final bool enableStackTrace; - final bool enableCustomParameters; - final bool handleWhenRejected; - ConsoleHandler({ this.enableDeviceParameters = true, this.enableApplicationParameters = true, @@ -18,11 +12,19 @@ class ConsoleHandler extends ReportHandler { this.handleWhenRejected = false, }); + final bool enableDeviceParameters; + final bool enableApplicationParameters; + final bool enableStackTrace; + final bool enableCustomParameters; + final bool handleWhenRejected; + @override Future handle(Report report, BuildContext? context) { logger ..info( - '============================ CATCHER LOG ============================', + '============================== ' + 'CATCHER 2 LOG ' + '==============================', ) ..info('Crash occurred on ${report.dateTime}') ..info(''); @@ -39,7 +41,7 @@ class ConsoleHandler extends ReportHandler { ..info('${report.error}') ..info(''); if (enableStackTrace) { - _printStackTraceFormatted(report.stackTrace as StackTrace?); + _printStackTraceFormatted(report.stackTrace); } if (enableCustomParameters) { _printCustomParametersFormatted(report.customParameters); @@ -73,11 +75,9 @@ class ConsoleHandler extends ReportHandler { } } - void _printStackTraceFormatted(StackTrace? stackTrace) { + void _printStackTraceFormatted(stackTrace) { logger.info('------- STACK TRACE -------'); - for (final entry in stackTrace.toString().split('\n')) { - logger.info(entry); - } + stackTrace?.toString().split('\n').forEach((entry) => logger.info(entry)); } @override @@ -91,7 +91,5 @@ class ConsoleHandler extends ReportHandler { ]; @override - bool shouldHandleWhenRejected() { - return handleWhenRejected; - } + bool shouldHandleWhenRejected() => handleWhenRejected; } diff --git a/lib/handlers/discord_handler.dart b/lib/handlers/discord_handler.dart index 141266a4..f021eb0c 100644 --- a/lib/handlers/discord_handler.dart +++ b/lib/handlers/discord_handler.dart @@ -1,14 +1,24 @@ import 'dart:async'; -import 'dart:io'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/utils/catcher_utils.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/utils/catcher_2_utils.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class DiscordHandler extends ReportHandler { + DiscordHandler( + this.webhookUrl, { + this.printLogs = false, + this.enableDeviceParameters = false, + this.enableApplicationParameters = false, + this.enableStackTrace = false, + this.enableCustomParameters = false, + this.customMessageBuilder, + }); + final Dio _dio = Dio(); final String webhookUrl; @@ -20,23 +30,12 @@ class DiscordHandler extends ReportHandler { final bool enableCustomParameters; final FutureOr Function(Report report)? customMessageBuilder; - DiscordHandler( - this.webhookUrl, { - this.printLogs = false, - this.enableDeviceParameters = false, - this.enableApplicationParameters = false, - this.enableStackTrace = false, - this.enableCustomParameters = false, - this.customMessageBuilder, - }); - @override Future handle(Report report, BuildContext? context) async { - if (report.platformType != PlatformType.web) { - if (!(await CatcherUtils.isInternetConnectionAvailable())) { - _printLog('No internet connection available'); - return false; - } + if (report.platformType != PlatformType.web && + !(await Catcher2Utils.isInternetConnectionAvailable())) { + _printLog('No internet connection available'); + return false; } var message = ''; @@ -107,30 +106,36 @@ class DiscordHandler extends ReportHandler { return stringBuffer.toString(); } - Future _sendContent(String content, File? screenshot) async { + Future _sendContent(String content, XFile? screenshot) async { try { _printLog('Sending request to Discord server...'); Response? response; + + final data = { + 'content': content, + }; + if (screenshot != null) { - final screenshotPath = screenshot.path; - final formData = FormData.fromMap({ - 'content': content, - 'file': await MultipartFile.fromFile(screenshotPath), - }); - response = await _dio.post(webhookUrl, data: formData); - } else { - final data = { - 'content': content, - }; - response = await _dio.post(webhookUrl, data: data); + data.addAll( + { + 'file': MultipartFile.fromBytes( + await screenshot.readAsBytes(), + filename: screenshot.name, + ), + }, + ); } + response = await _dio.post( + webhookUrl, + data: FormData.fromMap(data), + ); + _printLog( - 'Server responded with code: ${response.statusCode} and message:' - ' ${response.statusMessage}', + 'Server responded with code: ${response.statusCode} and message: ' + '${response.statusMessage}', ); - final statusCode = response.statusCode ?? 0; - return statusCode >= 200 && statusCode < 300; + return response.ok; } catch (exception) { _printLog('Failed to send data to Discord server: $exception'); return false; diff --git a/lib/handlers/email_auto_handler.dart b/lib/handlers/email_auto_handler.dart index 48519a9c..0912fee6 100644 --- a/lib/handlers/email_auto_handler.dart +++ b/lib/handlers/email_auto_handler.dart @@ -1,21 +1,15 @@ -import 'package:catcher/handlers/base_email_handler.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; +import 'dart:async'; + +import 'package:catcher_2/catcher_2.dart'; +import 'package:catcher_2/handlers/base_email_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:flutter/material.dart'; import 'package:mailer/mailer.dart'; import 'package:mailer/smtp_server.dart'; class EmailAutoHandler extends BaseEmailHandler { - final String smtpHost; - final int smtpPort; - final String senderEmail; - final String senderName; - final String senderPassword; - final bool enableSsl; - final List recipients; - final bool sendHtml; - final bool printLogs; - EmailAutoHandler( this.smtpHost, this.smtpPort, @@ -23,29 +17,31 @@ class EmailAutoHandler extends BaseEmailHandler { this.senderName, this.senderPassword, this.recipients, { + this.senderUsername, this.enableSsl = false, this.sendHtml = true, this.printLogs = false, - String? emailTitle, - String? emailHeader, - bool enableDeviceParameters = true, - bool enableApplicationParameters = true, - bool enableStackTrace = true, - bool enableCustomParameters = true, - }) : assert(recipients.isNotEmpty, "Recipients can't be null or empty"), - super( - enableDeviceParameters, - enableApplicationParameters, - enableStackTrace, - enableCustomParameters, - emailTitle, - emailHeader, - ); + super.emailTitle, + super.emailHeader, + super.enableDeviceParameters = true, + super.enableApplicationParameters = true, + super.enableStackTrace = true, + super.enableCustomParameters = true, + }) : assert(recipients.isNotEmpty, "Recipients can't be null or empty"); + final String smtpHost; + final int smtpPort; + final String senderEmail; + final String senderName; + final String senderPassword; + final String? senderUsername; + final bool enableSsl; + final List recipients; + final bool sendHtml; + final bool printLogs; @override - Future handle(Report report, BuildContext? context) { - return _sendMail(report); - } + Future handle(Report report, BuildContext? context) => + _sendMail(report); Future _sendMail(Report report) async { try { @@ -56,7 +52,7 @@ class EmailAutoHandler extends BaseEmailHandler { ..text = setupRawMessageText(report); if (report.screenshot != null) { - message.attachments = [FileAttachment(report.screenshot!)]; + message.attachments = [XFilePngAttachment(report.screenshot!)]; } if (sendHtml) { @@ -80,15 +76,13 @@ class EmailAutoHandler extends BaseEmailHandler { } } - SmtpServer _setupSmtpServer() { - return SmtpServer( - smtpHost, - port: smtpPort, - ssl: enableSsl, - username: senderEmail, - password: senderPassword, - ); - } + SmtpServer _setupSmtpServer() => SmtpServer( + smtpHost, + port: smtpPort, + ssl: enableSsl, + username: senderUsername ?? senderEmail, + password: senderPassword, + ); void _printLog(String log) { if (printLogs) { @@ -106,3 +100,14 @@ class EmailAutoHandler extends BaseEmailHandler { PlatformType.windows, ]; } + +class XFilePngAttachment extends Attachment { + XFilePngAttachment(this._xFile) { + contentType = 'image/png'; + } + + final XFile _xFile; + + @override + Stream> asStream() => _xFile.openRead(); +} diff --git a/lib/handlers/email_manual_handler.dart b/lib/handlers/email_manual_handler.dart index 95917e78..73a9b015 100644 --- a/lib/handlers/email_manual_handler.dart +++ b/lib/handlers/email_manual_handler.dart @@ -1,38 +1,28 @@ -import 'package:catcher/handlers/base_email_handler.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; +import 'package:catcher_2/handlers/base_email_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; class EmailManualHandler extends BaseEmailHandler { - final List recipients; - final bool sendHtml; - final bool printLogs; - EmailManualHandler( this.recipients, { this.sendHtml = true, this.printLogs = false, - String? emailTitle, - String? emailHeader, - bool enableDeviceParameters = true, - bool enableApplicationParameters = true, - bool enableStackTrace = true, - bool enableCustomParameters = true, - }) : assert(recipients.isNotEmpty, "Recipients can't be null or empty"), - super( - enableDeviceParameters, - enableApplicationParameters, - enableStackTrace, - enableCustomParameters, - emailTitle, - emailHeader, - ); + super.emailTitle, + super.emailHeader, + super.enableDeviceParameters = true, + super.enableApplicationParameters = true, + super.enableStackTrace = true, + super.enableCustomParameters = true, + }) : assert(recipients.isNotEmpty, "Recipients can't be empty"); + final List recipients; + final bool sendHtml; + final bool printLogs; @override - Future handle(Report report, BuildContext? context) async { - return _sendEmail(report); - } + Future handle(Report report, BuildContext? context) async => + _sendEmail(report); Future _sendEmail(Report report) async { try { diff --git a/lib/handlers/file_handler.dart b/lib/handlers/file_handler.dart index d96cfcd2..4e8733ba 100644 --- a/lib/handlers/file_handler.dart +++ b/lib/handlers/file_handler.dart @@ -1,13 +1,31 @@ import 'dart:convert'; import 'dart:io'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; import 'package:flutter/material.dart'; +typedef FileSupplier = File Function(Report); + class FileHandler extends ReportHandler { + FileHandler( + this.file, { + this.fileSupplier, + this.enableDeviceParameters = true, + this.enableApplicationParameters = true, + this.enableStackTrace = true, + this.enableCustomParameters = true, + this.printLogs = false, + this.handleWhenRejected = false, + }); + + /// A file that should be written to. Is overwritten by [fileSupplier]. final File file; + + /// Function that returns a file that should be written to. If this is set, + /// [file] has no effect. + final FileSupplier? fileSupplier; final bool enableDeviceParameters; final bool enableApplicationParameters; final bool enableStackTrace; @@ -15,22 +33,14 @@ class FileHandler extends ReportHandler { final bool printLogs; final bool handleWhenRejected; - late IOSink _sink; + File? _openedFile; + IOSink? _sink; bool _fileValidated = false; bool _fileValidationResult = false; - FileHandler( - this.file, { - this.enableDeviceParameters = true, - this.enableApplicationParameters = true, - this.enableStackTrace = true, - this.enableCustomParameters = true, - this.printLogs = false, - this.handleWhenRejected = false, - }); - @override Future handle(Report report, BuildContext? context) async { + _openedFile = fileSupplier != null ? fileSupplier!(report) : file; try { if (!_fileValidated) { _fileValidationResult = await _checkFile(); @@ -55,12 +65,15 @@ class FileHandler extends ReportHandler { } Future _checkFile() async { + if (_openedFile == null) { + return false; + } try { - final exists = file.existsSync(); + final exists = _openedFile!.existsSync(); if (!exists) { - file.createSync(); + _openedFile!.createSync(); } - final sink = file.openWrite(mode: FileMode.append)..write(''); + final sink = _openedFile!.openWrite(mode: FileMode.append)..write(''); await sink.flush(); await sink.close(); return true; @@ -71,24 +84,30 @@ class FileHandler extends ReportHandler { } Future _openFile() async { - _sink = file.openWrite(mode: FileMode.append); + if (_openedFile == null) { + _printLog('Could not open file'); + return; + } + _sink = _openedFile!.openWrite(mode: FileMode.append); _printLog('Opened file'); } void _writeLineToFile(String text) { - _sink.add(utf8.encode('$text\n')); + _sink?.add(utf8.encode('$text\n')); } Future _closeFile() async { - await _sink.flush(); - await _sink.close(); + await _sink?.flush(); + await _sink?.close(); _printLog('Closed file'); } Future _writeReportToFile(Report report) async { _printLog('Writing report to file'); _writeLineToFile( - '============================ CATCHER LOG ============================', + '============================== ' + 'CATCHER 2 LOG ' + '==============================', ); _writeLineToFile('Crash occurred on ${report.dateTime}'); _writeLineToFile(''); @@ -154,7 +173,5 @@ class FileHandler extends ReportHandler { ]; @override - bool shouldHandleWhenRejected() { - return handleWhenRejected; - } + bool shouldHandleWhenRejected() => handleWhenRejected; } diff --git a/lib/handlers/http_handler.dart b/lib/handlers/http_handler.dart index b8fd0b1a..5334ad47 100644 --- a/lib/handlers/http_handler.dart +++ b/lib/handlers/http_handler.dart @@ -1,47 +1,45 @@ import 'dart:collection'; -import 'package:catcher/model/http_request_type.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/utils/catcher_utils.dart'; +import 'package:catcher_2/model/http_request_type.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/utils/catcher_2_utils.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class HttpHandler extends ReportHandler { - final Dio _dio = Dio(); - - final HttpRequestType requestType; - final Uri endpointUri; - final Map headers; - final int requestTimeout; - final int responseTimeout; - final bool printLogs; - final bool enableDeviceParameters; - final bool enableApplicationParameters; - final bool enableStackTrace; - final bool enableCustomParameters; - HttpHandler( this.requestType, this.endpointUri, { Map? headers, - this.requestTimeout = 5000, - this.responseTimeout = 5000, + this.requestTimeout = const Duration(seconds: 5), + this.responseTimeout = const Duration(seconds: 5), this.printLogs = false, this.enableDeviceParameters = true, this.enableApplicationParameters = true, this.enableStackTrace = true, this.enableCustomParameters = false, }) : headers = headers ?? {}; + final Dio _dio = Dio(); + + final HttpRequestType requestType; + final Uri endpointUri; + final Map headers; + final Duration requestTimeout; + final Duration responseTimeout; + final bool printLogs; + final bool enableDeviceParameters; + final bool enableApplicationParameters; + final bool enableStackTrace; + final bool enableCustomParameters; @override Future handle(Report report, BuildContext? context) async { - if (report.platformType != PlatformType.web) { - if (!(await CatcherUtils.isInternetConnectionAvailable())) { - _printLog('No internet connection available'); - return false; - } + if (report.platformType != PlatformType.web && + !(await Catcher2Utils.isInternetConnectionAvailable())) { + _printLog('No internet connection available'); + return false; } if (requestType == HttpRequestType.post) { @@ -59,23 +57,25 @@ class HttpHandler extends ReportHandler { enableCustomParameters: enableCustomParameters, ); final mutableHeaders = HashMap(); - if (headers.isNotEmpty == true) { + if (headers.isNotEmpty) { mutableHeaders.addAll(headers); } final options = Options( - sendTimeout: Duration(milliseconds: requestTimeout), - receiveTimeout: Duration(milliseconds: responseTimeout), + sendTimeout: requestTimeout, + receiveTimeout: responseTimeout, headers: mutableHeaders, ); Response? response; _printLog('Calling: $endpointUri'); if (report.screenshot != null) { - final screenshotPath = report.screenshot?.path ?? ''; final formData = FormData.fromMap({ 'payload_json': json, - 'file': await MultipartFile.fromFile(screenshotPath), + 'file': MultipartFile.fromBytes( + await report.screenshot!.readAsBytes(), + filename: report.screenshot!.name, + ), }); response = await _dio.post( endpointUri.toString(), @@ -90,8 +90,8 @@ class HttpHandler extends ReportHandler { ); } _printLog( - 'HttpHandler response status: ${response.statusCode!} body:' - ' ${response.data!}', + 'HttpHandler response status: ${response.statusCode!} ' + 'body: ${response.data!}', ); return true; } catch (error, stackTrace) { @@ -107,9 +107,7 @@ class HttpHandler extends ReportHandler { } @override - String toString() { - return 'HttpHandler'; - } + String toString() => 'HttpHandler'; @override List getSupportedPlatforms() => [ diff --git a/lib/handlers/sentry_handler.dart b/lib/handlers/sentry_handler.dart index 0fc4cd3c..090ac6f9 100644 --- a/lib/handlers/sentry_handler.dart +++ b/lib/handlers/sentry_handler.dart @@ -1,45 +1,54 @@ -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:sentry/sentry.dart'; class SentryHandler extends ReportHandler { - ///Sentry Client instance + SentryHandler( + this.sentryClient, { + this.userContext, + this.serverName = 'Catcher 2', + this.loggerName = 'Catcher 2', + this.enableDeviceParameters = true, + this.enableApplicationParameters = true, + this.enableCustomParameters = true, + this.printLogs = true, + this.customEnvironment, + this.customRelease, + }); + + /// Sentry Client instance final SentryClient sentryClient; - ///User data + /// User data SentryUser? userContext; - ///Enable device parameters to be generated by Catcher + /// The server name to send + final String serverName; + + /// The logger name to send + final String loggerName; + + /// Enable device parameters to be generated by Catcher 2 final bool enableDeviceParameters; - ///Enable application parameters to be generated by Catcher + /// Enable application parameters to be generated by Catcher 2 final bool enableApplicationParameters; - ///Enable custom parameters to be generated by Catcher + /// Enable custom parameters to be generated by Catcher 2 final bool enableCustomParameters; - ///Custom environment, if null, Catcher will generate it + /// Enable additional logs printing + final bool printLogs; + + /// Custom environment; if `null`, Catcher 2 will generate it final String? customEnvironment; - ///Custom release, if null, Catcher will generate it + /// Custom release; if `null`, Catcher 2 will generate it final String? customRelease; - ///Enable additional logs printing - final bool printLogs; - - SentryHandler( - this.sentryClient, { - this.userContext, - this.enableDeviceParameters = true, - this.enableApplicationParameters = true, - this.enableCustomParameters = true, - this.printLogs = true, - this.customEnvironment, - this.customRelease, - }); - @override Future handle(Report report, BuildContext? context) async { try { @@ -57,7 +66,29 @@ class SentryHandler extends ReportHandler { } final event = buildEvent(report, tags); - await sentryClient.captureEvent(event); + + // If we have a screenshot and we're not in web, then upload screenshot + // to Sentry. Screenshot isn't supported in web (not by Sentry or catcher) + // and the code relies on File from dart:io that does not work in web + // either because we do not have access to the file system in web. + SentryAttachment? screenshotAttachment; + try { + if (report.screenshot != null && !kIsWeb) { + final bytes = await report.screenshot!.readAsBytes(); + screenshotAttachment = SentryAttachment.fromScreenshotData(bytes); + _printLog('Created screenshot attachment'); + } + } catch (exception, stackTrace) { + _printLog('Failed to read screenshot data: $exception $stackTrace'); + } + + await sentryClient.captureEvent( + event, + stackTrace: report.stackTrace, + hint: screenshotAttachment != null + ? Hint.withScreenshot(screenshotAttachment) + : null, + ); _printLog('Logged to sentry!'); return true; @@ -71,10 +102,11 @@ class SentryHandler extends ReportHandler { var applicationVersion = ''; final applicationParameters = report.applicationParameters; if (applicationParameters.containsKey('appName')) { - applicationVersion += (applicationParameters['appName'] as String?)!; + applicationVersion += + (applicationParameters['appName'] as String?)!.toLowerCase(); } if (applicationParameters.containsKey('version')) { - applicationVersion += ' ${applicationParameters['version']}'; + applicationVersion += "@${applicationParameters["version"]}"; } if (applicationVersion.isEmpty) { applicationVersion = '?'; @@ -82,30 +114,26 @@ class SentryHandler extends ReportHandler { return applicationVersion; } - SentryEvent buildEvent(Report report, Map tags) { - return SentryEvent( - logger: 'Catcher', - serverName: 'Catcher', - release: customRelease ?? _getApplicationVersion(report), - environment: customEnvironment ?? - (report.applicationParameters['environment'] as String?), - message: const SentryMessage('Error handled by Catcher'), - throwable: report.error, - level: SentryLevel.error, - culprit: '', - tags: changeToSentryMap(tags), - user: userContext, - ); - } + SentryEvent buildEvent(Report report, Map tags) => + SentryEvent( + logger: loggerName, + serverName: serverName, + release: customRelease ?? _getApplicationVersion(report), + environment: customEnvironment ?? + (report.applicationParameters['environment'] as String?), + message: SentryMessage(report.error.toString()), + throwable: report.error, + level: SentryLevel.error, + culprit: '', + tags: changeToSentryMap(tags), + user: userContext, + ); Map changeToSentryMap(Map map) { final sentryMap = {}; - map.forEach((key, dynamic value) { - if (value.toString().isEmpty) { - sentryMap[key] = 'none'; - } else { - sentryMap[key] = value.toString(); - } + map.forEach((key, value) { + final val = value.toString(); + sentryMap[key] = val.isNotEmpty ? val : 'none'; }); return sentryMap; } diff --git a/lib/handlers/slack_handler.dart b/lib/handlers/slack_handler.dart index b0beda31..0b1d371d 100644 --- a/lib/handlers/slack_handler.dart +++ b/lib/handlers/slack_handler.dart @@ -1,18 +1,36 @@ import 'dart:async'; +import 'dart:convert'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/utils/catcher_utils.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/utils/catcher_2_utils.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -//Slack webhook API doesn't allow file attachments class SlackHandler extends ReportHandler { + SlackHandler( + this.webhookUrl, + this.channel, { + this.apiToken, + this.channelId, + this.username = 'Catcher 2', + this.iconEmoji = ':bangbang:', + this.printLogs = false, + this.enableDeviceParameters = false, + this.enableApplicationParameters = false, + this.enableStackTrace = false, + this.enableCustomParameters = false, + this.customMessageBuilder, + }); + final Dio _dio = Dio(); final String webhookUrl; + final String? apiToken; final String channel; + final String? channelId; final String username; final String iconEmoji; @@ -23,23 +41,10 @@ class SlackHandler extends ReportHandler { final bool enableCustomParameters; final FutureOr Function(Report report)? customMessageBuilder; - SlackHandler( - this.webhookUrl, - this.channel, { - this.username = 'Catcher', - this.iconEmoji = ':bangbang:', - this.printLogs = false, - this.enableDeviceParameters = false, - this.enableApplicationParameters = false, - this.enableStackTrace = false, - this.enableCustomParameters = false, - this.customMessageBuilder, - }); - @override Future handle(Report report, BuildContext? context) async { try { - if (!(await CatcherUtils.isInternetConnectionAvailable())) { + if (!(await Catcher2Utils.isInternetConnectionAvailable())) { _printLog('No internet connection available'); return false; } @@ -50,26 +55,141 @@ class SlackHandler extends ReportHandler { message = _buildMessage(report); } - final data = { + final screenshot = report.screenshot; + + final data = { 'text': message, 'channel': channel, 'username': username, 'icon_emoji': iconEmoji, }; _printLog('Sending request to Slack server...'); - final response = await _dio.post(webhookUrl, data: data); + + if (screenshot != null) { + data.addAll( + await _tryUploadScreenshot(screenshot: screenshot), + ); + } + + final response = await _dio.post( + webhookUrl, + data: json.encode(data), + options: Options(contentType: Headers.formUrlEncodedContentType), + ); _printLog( - 'Server responded with code: ${response.statusCode} and message:' - ' ${response.statusMessage}', + 'Server responded with code: ${response.statusCode} and ' + 'message: ${response.statusMessage}', ); - final statusCode = response.statusCode ?? 0; - return statusCode >= 200 && statusCode < 300; + + return response.ok; } catch (exception) { _printLog('Failed to send slack message: $exception'); return false; } } + Future> _tryUploadScreenshot({ + required XFile screenshot, + }) async { + if (apiToken == null || channelId == null) { + _printLog( + 'Cannot send screenshot to Slack because either ' + 'apiToken or channelId is not set!', + ); + return {}; + } + + try { + final name = screenshot.name; + + final formData = FormData.fromMap({ + 'token': apiToken, + 'filename': name, + 'length': await screenshot.length(), + 'alt_txt': 'Error Screenshot', + }); + final responseFile = await _dio.post( + 'https://slack.com/api/files.getUploadURLExternal', + data: formData, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + if (responseFile.data == null || + responseFile.data['ok'] != true || + responseFile.data['upload_url'] == null || + responseFile.data['file_id'] == null) { + _printLog( + 'Server responded to getUploadURLExternal with code: ' + '${responseFile.statusCode} ' + 'and message upload file: ${responseFile.statusMessage}', + ); + return {}; + } + + final formDataPost = FormData.fromMap({ + 'token': apiToken, + 'file': MultipartFile.fromBytes( + await screenshot.readAsBytes(), + filename: screenshot.name, + ), + }); + final responseFilePost = await _dio.post( + responseFile.data['upload_url'], + data: formDataPost, + options: Options( + contentType: Headers.multipartFormDataContentType, + validateStatus: (e) => true, + ), + ); + if (!responseFilePost.ok) { + _printLog( + 'Server responded to upload file post with code: ' + '${responseFilePost.statusCode} ' + 'and message upload file: ${responseFilePost.statusMessage}', + ); + return {}; + } + + final formDataComplete = FormData.fromMap({ + 'token': apiToken, + 'files': '[{"id":"${responseFile.data['file_id']}"}]', + 'channel_id': channelId, + }); + final responseFileComplete = await _dio.post( + 'https://slack.com/api/files.completeUploadExternal', + data: formDataComplete, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + + _printLog( + 'Server responded to completeUploadExternal with code: ' + '${responseFileComplete.statusCode} ' + 'and message upload file: ${responseFileComplete.statusMessage}', + ); + + if (responseFileComplete.data == null || + responseFileComplete.data['ok'] != true) { + return {}; + } + + return { + 'attachments': [ + { + 'image_url': responseFileComplete.data['files'][0]['url_private'], + 'text': 'Screenshot will soon be available here: ' + '${responseFileComplete.data['files'][0]['permalink']}', + }, + ], + }; + } catch (exception) { + _printLog('Failed to send screenshot: $exception'); + return {}; + } + } + String _buildMessage(Report report) { final stringBuffer = StringBuffer() ..write('*Error:* ```${report.error}```\n'); diff --git a/lib/handlers/snackbar_handler.dart b/lib/handlers/snackbar_handler.dart index 2aab90c3..4470a5d7 100644 --- a/lib/handlers/snackbar_handler.dart +++ b/lib/handlers/snackbar_handler.dart @@ -1,71 +1,71 @@ -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; import 'package:flutter/material.dart'; -///Handler which displays error report as snack bar. +/// Handler which displays error report as snack bar. class SnackbarHandler extends ReportHandler { - ///See [SnackBar] docs for details. + SnackbarHandler( + this.duration, { + this.backgroundColor, + this.elevation, + this.margin, + this.padding, + this.width, + this.shape, + this.behavior, + this.action, + this.animation, + this.onVisible, + this.customMessage, + this.textStyle, + this.printLogs = false, + }); + + /// See [SnackBar] docs for details. final Duration duration; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final Color? backgroundColor; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final double? elevation; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final EdgeInsetsGeometry? margin; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final EdgeInsetsGeometry? padding; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final double? width; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final ShapeBorder? shape; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final SnackBarBehavior? behavior; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final SnackBarAction? action; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final Animation? animation; - ///See [SnackBar] docs for details. + /// See [SnackBar] docs for details. final VoidCallback? onVisible; - ///Custom message which can be displayed instead default one. + /// Custom message which can be displayed instead default one. final String? customMessage; - ///Custom text style for text displayed within snackbar. + /// Custom text style for text displayed within snackbar. final TextStyle? textStyle; - ///Enable additional logs printing + /// Enable additional logs printing final bool printLogs; - SnackbarHandler( - this.duration, { - this.backgroundColor, - this.elevation, - this.margin, - this.padding, - this.width, - this.shape, - this.behavior, - this.action, - this.animation, - this.onVisible, - this.customMessage, - this.textStyle, - this.printLogs = false, - }); - - ///Handle report. If there's scaffold messenger in provided context, then - ///snackbar will be shown. + /// Handle report. If there's scaffold messenger in provided context, then + /// snackbar will be shown. @override Future handle(Report error, BuildContext? context) async { try { @@ -100,7 +100,7 @@ class SnackbarHandler extends ReportHandler { } } - ///Checks whether context has scaffold messenger. + /// Checks whether context has scaffold messenger. bool _hasScaffoldMessenger(BuildContext context) { try { return context.findAncestorWidgetOfExactType() != null; @@ -110,9 +110,9 @@ class SnackbarHandler extends ReportHandler { } } - ///Get error message based on configuration and report. + /// Get error message based on configuration and report. String _getErrorMessage(Report error) { - if (customMessage?.isNotEmpty == true) { + if (customMessage?.isNotEmpty ?? false) { return customMessage!; } else { return '${localizationOptions.toastHandlerDescription} ${error.error}'; @@ -126,9 +126,7 @@ class SnackbarHandler extends ReportHandler { } @override - bool isContextRequired() { - return true; - } + bool isContextRequired() => true; @override List getSupportedPlatforms() => [ diff --git a/lib/handlers/toast_handler.dart b/lib/handlers/toast_handler.dart index 69b883ed..43a3f0ed 100644 --- a/lib/handlers/toast_handler.dart +++ b/lib/handlers/toast_handler.dart @@ -1,22 +1,13 @@ -import 'package:catcher/core/application_profile_manager.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/model/toast_handler_gravity.dart'; -import 'package:catcher/model/toast_handler_length.dart'; +import 'package:catcher_2/core/application_profile_manager.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/model/toast_handler_gravity.dart'; +import 'package:catcher_2/model/toast_handler_length.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; class ToastHandler extends ReportHandler { - final ToastHandlerGravity gravity; - final ToastHandlerLength length; - final Color backgroundColor; - final Color textColor; - final double textSize; - final String? customMessage; - final bool handleWhenRejected; - FToast? fToast; - ToastHandler({ this.gravity = ToastHandlerGravity.bottom, this.length = ToastHandlerLength.long, @@ -27,6 +18,14 @@ class ToastHandler extends ReportHandler { this.handleWhenRejected = false, }); + final ToastHandlerGravity gravity; + final ToastHandlerLength length; + final Color backgroundColor; + final Color textColor; + final double textSize; + final String? customMessage; + final bool handleWhenRejected; + @override Future handle(Report report, BuildContext? context) async { if (ApplicationProfileManager.isAndroid() || @@ -45,8 +44,11 @@ class ToastHandler extends ReportHandler { Future.delayed( const Duration(milliseconds: 500), () { + if (context == null || !context.mounted) { + return; + } Navigator.push( - context!, + context, PageRouteBuilder( opaque: false, pageBuilder: (_, __, ___) => FlutterToastPage( @@ -77,29 +79,15 @@ class ToastHandler extends ReportHandler { } } - Toast _getLength() { - if (length == ToastHandlerLength.long) { - return Toast.LENGTH_LONG; - } else { - return Toast.LENGTH_SHORT; - } - } + Toast _getLength() => length == ToastHandlerLength.long + ? Toast.LENGTH_LONG + : Toast.LENGTH_SHORT; - int _getLengthIos() { - if (length == ToastHandlerLength.long) { - return 5; - } else { - return 1; - } - } + int _getLengthIos() => length == ToastHandlerLength.long ? 5 : 1; - String _getErrorMessage(Report error) { - if (customMessage?.isNotEmpty == true) { - return customMessage!; - } else { - return '${localizationOptions.toastHandlerDescription} ${error.error}'; - } - } + String _getErrorMessage(Report error) => customMessage?.isNotEmpty ?? false + ? customMessage! + : '${localizationOptions.toastHandlerDescription} ${error.error}'; @override List getSupportedPlatforms() => [ @@ -112,24 +100,13 @@ class ToastHandler extends ReportHandler { ]; @override - bool isContextRequired() { - return true; - } + bool isContextRequired() => true; @override - bool shouldHandleWhenRejected() { - return handleWhenRejected; - } + bool shouldHandleWhenRejected() => handleWhenRejected; } class FlutterToastPage extends StatefulWidget { - final String text; - final ToastGravity gravity; - final Duration duration; - final Color backgroundColor; - final Color textColor; - final double textSize; - const FlutterToastPage( this.text, this.gravity, @@ -140,10 +117,15 @@ class FlutterToastPage extends StatefulWidget { super.key, }); + final String text; + final ToastGravity gravity; + final Duration duration; + final Color backgroundColor; + final Color textColor; + final double textSize; + @override - State createState() { - return _FlutterToastPageState(); - } + State createState() => _FlutterToastPageState(); } class _FlutterToastPageState extends State { @@ -187,9 +169,7 @@ class _FlutterToastPageState extends State { } @override - Widget build(BuildContext context) { - return const SizedBox(); - } + Widget build(BuildContext context) => const SizedBox(); @override void dispose() { diff --git a/lib/mode/dialog_report_mode.dart b/lib/mode/dialog_report_mode.dart index f79444c7..0cf67f49 100644 --- a/lib/mode/dialog_report_mode.dart +++ b/lib/mode/dialog_report_mode.dart @@ -1,7 +1,7 @@ -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_mode.dart'; -import 'package:catcher/utils/catcher_utils.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_mode.dart'; +import 'package:catcher_2/utils/catcher_2_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -14,7 +14,10 @@ class DialogReportMode extends ReportMode { Future _showDialog(Report report, BuildContext? context) async { await Future.delayed(Duration.zero); if (context != null) { - if (CatcherUtils.isCupertinoAppAncestor(context)) { + if (!context.mounted) { + return; + } + if (Catcher2Utils.isCupertinoAppAncestor(context)) { return showCupertinoDialog( context: context, builder: (context) => _buildCupertinoDialog(report, context), @@ -29,49 +32,51 @@ class DialogReportMode extends ReportMode { } } - Widget _buildCupertinoDialog(Report report, BuildContext context) { - return PopScope( - onPopInvoked: (_) async { - super.onActionRejected(report); - }, - child: CupertinoAlertDialog( - title: Text(localizationOptions.dialogReportModeTitle), - content: Text(localizationOptions.dialogReportModeDescription), - actions: [ - CupertinoDialogAction( - onPressed: () => _onAcceptReportClicked(context, report), - child: Text(localizationOptions.dialogReportModeAccept), - ), - CupertinoDialogAction( - onPressed: () => _onCancelReportClicked(context, report), - child: Text(localizationOptions.dialogReportModeCancel), - ), - ], - ), - ); - } + Widget _buildCupertinoDialog(Report report, BuildContext context) => + // ignore: deprecated_member_use + WillPopScope( + onWillPop: () async { + super.onActionRejected(report); + return true; + }, + child: CupertinoAlertDialog( + title: Text(localizationOptions.dialogReportModeTitle), + content: Text(localizationOptions.dialogReportModeDescription), + actions: [ + CupertinoDialogAction( + onPressed: () => _onAcceptReportClicked(context, report), + child: Text(localizationOptions.dialogReportModeAccept), + ), + CupertinoDialogAction( + onPressed: () => _onCancelReportClicked(context, report), + child: Text(localizationOptions.dialogReportModeCancel), + ), + ], + ), + ); - Widget _buildMaterialDialog(Report report, BuildContext context) { - return PopScope( - onPopInvoked: (_) async { - super.onActionRejected(report); - }, - child: AlertDialog( - title: Text(localizationOptions.dialogReportModeTitle), - content: Text(localizationOptions.dialogReportModeDescription), - actions: [ - TextButton( - onPressed: () => _onAcceptReportClicked(context, report), - child: Text(localizationOptions.dialogReportModeAccept), - ), - TextButton( - onPressed: () => _onCancelReportClicked(context, report), - child: Text(localizationOptions.dialogReportModeCancel), - ), - ], - ), - ); - } + Widget _buildMaterialDialog(Report report, BuildContext context) => + // ignore: deprecated_member_use + WillPopScope( + onWillPop: () async { + super.onActionRejected(report); + return true; + }, + child: AlertDialog( + title: Text(localizationOptions.dialogReportModeTitle), + content: Text(localizationOptions.dialogReportModeDescription), + actions: [ + TextButton( + onPressed: () => _onAcceptReportClicked(context, report), + child: Text(localizationOptions.dialogReportModeAccept), + ), + TextButton( + onPressed: () => _onCancelReportClicked(context, report), + child: Text(localizationOptions.dialogReportModeCancel), + ), + ], + ), + ); void _onAcceptReportClicked(BuildContext context, Report report) { super.onActionConfirmed(report); @@ -84,9 +89,7 @@ class DialogReportMode extends ReportMode { } @override - bool isContextRequired() { - return true; - } + bool isContextRequired() => true; @override List getSupportedPlatforms() => [ diff --git a/lib/mode/page_report_mode.dart b/lib/mode/page_report_mode.dart index dc2862f7..4cd271e3 100644 --- a/lib/mode/page_report_mode.dart +++ b/lib/mode/page_report_mode.dart @@ -1,16 +1,16 @@ -import 'package:catcher/catcher.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/utils/catcher_utils.dart'; +import 'package:catcher_2/catcher_2.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/utils/catcher_2_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class PageReportMode extends ReportMode { - final bool showStackTrace; - PageReportMode({ this.showStackTrace = true, }); + final bool showStackTrace; + @override void requestAction(Report report, BuildContext? context) { if (context != null) { @@ -23,6 +23,9 @@ class PageReportMode extends ReportMode { BuildContext context, ) async { await Future.delayed(Duration.zero); + if (!context.mounted) { + return; + } await Navigator.push( context, MaterialPageRoute( @@ -32,9 +35,7 @@ class PageReportMode extends ReportMode { } @override - bool isContextRequired() { - return true; - } + bool isContextRequired() => true; @override List getSupportedPlatforms() => [ @@ -48,115 +49,109 @@ class PageReportMode extends ReportMode { } class PageWidget extends StatefulWidget { - final PageReportMode pageReportMode; - final Report report; - const PageWidget( this.pageReportMode, this.report, { super.key, }); + final PageReportMode pageReportMode; + final Report report; + @override - PageWidgetState createState() { - return PageWidgetState(); - } + PageWidgetState createState() => PageWidgetState(); } class PageWidgetState extends State { @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (_) async { - widget.pageReportMode.onActionRejected(widget.report); - }, - child: Builder( - builder: (context) => CatcherUtils.isCupertinoAppAncestor(context) - ? _buildCupertinoPage() - : _buildMaterialPage(), - ), - ); - } - - Widget _buildMaterialPage() { - return Scaffold( - appBar: AppBar( - title: - Text(widget.pageReportMode.localizationOptions.pageReportModeTitle), - ), - body: _buildInnerWidget(), - ); - } + // ignore: deprecated_member_use + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + widget.pageReportMode.onActionRejected(widget.report); + return true; + }, + child: Builder( + builder: (context) => Catcher2Utils.isCupertinoAppAncestor(context) + ? _buildCupertinoPage(context) + : _buildMaterialPage(context), + ), + ); - Widget _buildCupertinoPage() { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: - Text(widget.pageReportMode.localizationOptions.pageReportModeTitle), - ), - child: SafeArea( - child: _buildInnerWidget(), - ), - ); - } + Widget _buildMaterialPage(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text( + widget.pageReportMode.localizationOptions.pageReportModeTitle, + ), + ), + body: _buildInnerWidget(context), + ); - Widget _buildInnerWidget() { - return Container( - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: const BoxDecoration(color: Colors.white), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 10), + Widget _buildCupertinoPage(BuildContext context) => CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text( + widget.pageReportMode.localizationOptions.pageReportModeTitle, ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - widget - .pageReportMode.localizationOptions.pageReportModeDescription, - style: _getTextStyle(15), - textAlign: TextAlign.center, + ), + child: SafeArea( + child: _buildInnerWidget(context), + ), + ); + + Widget _buildInnerWidget(BuildContext context) => Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 10), ), - ), - const Padding( - padding: EdgeInsets.only(top: 20), - ), - Expanded( - child: Padding( + Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: _getStackTraceWidget(), + child: Text( + widget.pageReportMode.localizationOptions + .pageReportModeDescription, + style: _getTextStyle(15), + textAlign: TextAlign.center, + ), ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: _onAcceptClicked, - child: Text( - widget - .pageReportMode.localizationOptions.pageReportModeAccept, - ), + const Padding( + padding: EdgeInsets.only(top: 20), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _getStackTraceWidget(), ), - TextButton( - onPressed: _onCancelClicked, - child: Text( - widget - .pageReportMode.localizationOptions.pageReportModeCancel, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: _onAcceptClicked, + child: Text( + widget.pageReportMode.localizationOptions + .pageReportModeAccept, + ), ), - ), - ], - ), - ], - ), - ); - } + TextButton( + onPressed: _onCancelClicked, + child: Text( + widget.pageReportMode.localizationOptions + .pageReportModeCancel, + ), + ), + ], + ), + ], + ), + ); - TextStyle _getTextStyle(double fontSize) { - return TextStyle( - fontSize: fontSize, - decoration: TextDecoration.none, - ); - } + TextStyle _getTextStyle(double fontSize) => TextStyle( + fontSize: fontSize, + decoration: TextDecoration.none, + ); Widget _getStackTraceWidget() { if (widget.pageReportMode.showStackTrace) { @@ -176,13 +171,10 @@ class PageWidgetState extends State { child: ListView.builder( padding: const EdgeInsets.all(8), itemCount: items.length, - itemBuilder: (BuildContext context, int index) { - return Text( - // ignore: unnecessary_string_interpolations - '${items[index]}', - style: _getTextStyle(10), - ); - }, + itemBuilder: (context, index) => Text( + items[index], + style: _getTextStyle(10), + ), ), ); } else { diff --git a/lib/mode/report_mode_action_confirmed.dart b/lib/mode/report_mode_action_confirmed.dart index c674ab1d..9dd58eab 100644 --- a/lib/mode/report_mode_action_confirmed.dart +++ b/lib/mode/report_mode_action_confirmed.dart @@ -1,7 +1,7 @@ -import 'package:catcher/model/report.dart'; +import 'package:catcher_2/model/report.dart'; abstract class ReportModeAction { - ///Code which should be triggered if report mode has been confirmed + /// Code which should be triggered if report mode has been confirmed void onActionConfirmed(Report report); /// Code which should be triggered if report mode has been rejected diff --git a/lib/mode/silent_report_mode.dart b/lib/mode/silent_report_mode.dart index 8f8e5de9..ea4811d8 100644 --- a/lib/mode/silent_report_mode.dart +++ b/lib/mode/silent_report_mode.dart @@ -1,6 +1,6 @@ -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_mode.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_mode.dart'; import 'package:flutter/widgets.dart'; class SilentReportMode extends ReportMode { diff --git a/lib/model/application_profile.dart b/lib/model/application_profile.dart index 4b6c73f8..48cea7e7 100644 --- a/lib/model/application_profile.dart +++ b/lib/model/application_profile.dart @@ -1,11 +1,11 @@ -///Available application run modes. +/// Available application run modes. enum ApplicationProfile { - ///Debug mode + /// Debug mode debug, - ///Release mode + /// Release mode release, - ///Profile mode + /// Profile mode profile, } diff --git a/lib/model/catcher_options.dart b/lib/model/catcher_2_options.dart similarity index 54% rename from lib/model/catcher_options.dart rename to lib/model/catcher_2_options.dart index 42010a19..678deb8f 100644 --- a/lib/model/catcher_options.dart +++ b/lib/model/catcher_2_options.dart @@ -1,61 +1,16 @@ -import 'package:catcher/handlers/console_handler.dart'; -import 'package:catcher/mode/dialog_report_mode.dart'; -import 'package:catcher/mode/silent_report_mode.dart'; -import 'package:catcher/model/localization_options.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/model/report_handler.dart'; -import 'package:catcher/model/report_mode.dart'; -import 'package:catcher/utils/catcher_logger.dart'; - -class CatcherOptions { - /// Handlers that should be used - final List handlers; - - /// Timeout for handlers which uses long-running action. In milliseconds. - final int handlerTimeout; - - /// Report mode that should be called if new report appears - final ReportMode reportMode; - - /// Localization options (translations) - final List localizationOptions; - - /// Explicit report modes map which will be used to trigger specific report - /// mode for specific error - final Map explicitExceptionReportModesMap; - - /// Explicit report handler map which will be used to trigger specific report - /// report handler for specific error - final Map explicitExceptionHandlersMap; - - /// Custom parameters which will be used in report handler - final Map customParameters; - - ///Should catcher handle silent errors - final bool handleSilentError; - - ///Path which will be used to save temp. screenshots. If not set, Catcher - ///will use temp directory. - final String screenshotsPath; - - ///Parameters which will be excluded from report - final List excludedParameters; - - ///Function which is used to filter reports. If [filterFunction] is empty then - ///all reports will be passed to handlers. - ///To mark given Report as valid, [filterFunction] should return true, - ///otherwise return false. - final bool Function(Report report)? filterFunction; - - ///Timeout for reports to prevent handling duplicates of same error. In - ///milliseconds. - final int reportOccurrenceTimeout; - - ///Logger instance. - final CatcherLogger? logger; - - /// Builds catcher options instance - CatcherOptions( +import 'package:catcher_2/handlers/console_handler.dart'; +import 'package:catcher_2/mode/dialog_report_mode.dart'; +import 'package:catcher_2/mode/silent_report_mode.dart'; +import 'package:catcher_2/model/localization_options.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/model/report_handler.dart'; +import 'package:catcher_2/model/report_mode.dart'; +import 'package:catcher_2/utils/catcher_2_logger.dart'; +import 'package:flutter/foundation.dart'; + +class Catcher2Options { + /// Builds catcher 2 options instance + Catcher2Options( this.reportMode, this.handlers, { this.handlerTimeout = 5000, @@ -68,11 +23,13 @@ class CatcherOptions { this.excludedParameters = const [], this.filterFunction, this.reportOccurrenceTimeout = 3000, + this.onFlutterError, + this.onPlatformError, this.logger, }); - /// Builds default catcher options release instance - CatcherOptions.getDefaultReleaseOptions() + /// Builds default catcher 2 options release instance + Catcher2Options.getDefaultReleaseOptions() : handlers = [ConsoleHandler()], reportMode = DialogReportMode(), handlerTimeout = 5000, @@ -85,10 +42,12 @@ class CatcherOptions { excludedParameters = const [], filterFunction = null, reportOccurrenceTimeout = 3000, - logger = CatcherLogger(); + onFlutterError = null, + onPlatformError = null, + logger = Catcher2Logger(); - /// Builds default catcher options debug instance - CatcherOptions.getDefaultDebugOptions() + /// Builds default catcher 2 options debug instance + Catcher2Options.getDefaultDebugOptions() : handlers = [ConsoleHandler()], reportMode = SilentReportMode(), handlerTimeout = 10000, @@ -101,10 +60,12 @@ class CatcherOptions { excludedParameters = const [], filterFunction = null, reportOccurrenceTimeout = 3000, - logger = CatcherLogger(); + onFlutterError = null, + onPlatformError = null, + logger = Catcher2Logger(); - /// Builds default catcher options profile instance - CatcherOptions.getDefaultProfileOptions() + /// Builds default catcher 2 options profile instance + Catcher2Options.getDefaultProfileOptions() : handlers = [ConsoleHandler()], reportMode = SilentReportMode(), handlerTimeout = 10000, @@ -117,5 +78,63 @@ class CatcherOptions { excludedParameters = const [], filterFunction = null, reportOccurrenceTimeout = 3000, - logger = CatcherLogger(); + onFlutterError = null, + onPlatformError = null, + logger = Catcher2Logger(); + + /// Handlers that should be used + final List handlers; + + /// Timeout for handlers which uses long-running action. In milliseconds. + final int handlerTimeout; + + /// Report mode that should be called if new report appears + final ReportMode reportMode; + + /// Localization options (translations) + final List localizationOptions; + + /// Explicit report modes map which will be used to trigger specific report + /// mode for specific error + final Map explicitExceptionReportModesMap; + + /// Explicit report handler map which will be used to trigger specific report + /// report handler for specific error + final Map explicitExceptionHandlersMap; + + /// Custom parameters which will be used in report handler + final Map customParameters; + + /// Should catcher 2 handle silent errors + final bool handleSilentError; + + /// Path which will be used to save temp. screenshots. If not set, Catcher 2 + /// will use temp directory. + final String screenshotsPath; + + /// Parameters which will be excluded from report + final List excludedParameters; + + /// Function which is used to filter reports. If [filterFunction] is empty + /// then all reports will be passed to handlers. + /// To mark given Report as valid, [filterFunction] should return true, + /// otherwise return false. + final bool Function(Report report)? filterFunction; + + /// Timeout for reports to prevent handling duplicates of same error. In + /// milliseconds. + final int reportOccurrenceTimeout; + + /// Function which should additionally be called when an error is caught using + /// the [FlutterError.onError] mechanism. Useful if other packages also need + /// to override this behaviour (such as integration tests). + final void Function(FlutterErrorDetails details)? onFlutterError; + + /// Function which should additionally be called when an error is caught using + /// the [PlatformDispatcher.instance.onError] mechanism. Useful if other + /// packages also need to override this behaviour. + final void Function(Object error, StackTrace stack)? onPlatformError; + + /// Logger instance. + final Catcher2Logger? logger; } diff --git a/lib/model/localization_options.dart b/lib/model/localization_options.dart index 3f93364f..2be83d70 100644 --- a/lib/model/localization_options.dart +++ b/lib/model/localization_options.dart @@ -1,21 +1,4 @@ class LocalizationOptions { - final String languageCode; - final String notificationReportModeTitle; - final String notificationReportModeContent; - - final String dialogReportModeTitle; - final String dialogReportModeDescription; - final String dialogReportModeAccept; - final String dialogReportModeCancel; - - final String pageReportModeTitle; - final String pageReportModeDescription; - final String pageReportModeAccept; - final String pageReportModeCancel; - - final String toastHandlerDescription; - final String snackbarHandlerDescription; - LocalizationOptions( this.languageCode, { this.notificationReportModeTitle = 'Application error occurred', @@ -23,336 +6,390 @@ class LocalizationOptions { 'Click here to send error report to support team.', this.dialogReportModeTitle = 'Crash', this.dialogReportModeDescription = - 'Unexpected error occurred in application. Error report is ready to' - ' send to support team. Please click Accept to send error report ' - 'or Cancel to dismiss report.', + 'Unexpected error occurred in application. Error report is ready to ' + 'send to support team. Please click Accept to send error report or ' + 'Cancel to dismiss report.', this.dialogReportModeAccept = 'Accept', this.dialogReportModeCancel = 'Cancel', this.pageReportModeTitle = 'Crash', this.pageReportModeDescription = - 'Unexpected error occurred in application. Error report is ready to' - ' send to support team. Please click Accept to send error report ' - 'or Cancel to dismiss report.', + 'Unexpected error occurred in application. Error report is ready to ' + 'send to support team. Please click Accept to send error report or ' + 'Cancel to dismiss report.', this.pageReportModeAccept = 'Accept', this.pageReportModeCancel = 'Cancel', this.toastHandlerDescription = 'Error occurred:', this.snackbarHandlerDescription = 'Error occurred:', }); - static LocalizationOptions buildDefaultEnglishOptions() { - return LocalizationOptions('en'); - } + factory LocalizationOptions.buildDefaultEnglishOptions() => + LocalizationOptions('en'); + + factory LocalizationOptions.buildDefaultArabicOptions() => + LocalizationOptions( + 'ar', + notificationReportModeTitle: 'حدث خطأ في التطبيق', + notificationReportModeContent: + 'انقر هنا لإرسال تقرير الخطأ إلى فريق الدعم.', + dialogReportModeTitle: 'حدث خطأ', + dialogReportModeDescription: 'حدث خطأ غير متوقع في التطبيق.' + ' تقرير الخطأ جاهز للإرسال إلى فريق الدعم.' + ' الرجاء النقر فوق "قبول" لإرسال تقرير الخطأ' + ' أو "إلغاء" لرفض.', + dialogReportModeAccept: 'قبول', + dialogReportModeCancel: 'إلغاء', + pageReportModeTitle: 'حدث خطأ', + pageReportModeDescription: 'حدث خطأ غير متوقع في التطبيق.' + ' تقرير الخطأ جاهز للإرسال إلى فريق الدعم.' + ' الرجاء النقر فوق "قبول" لإرسال تقرير الخطأ' + ' أو "إلغاء" للرفض.', + pageReportModeAccept: 'قبول', + pageReportModeCancel: 'إلغاء', + toastHandlerDescription: 'حدث خطأ:', + snackbarHandlerDescription: 'حدث خطأ:', + ); - static LocalizationOptions buildDefaultChineseOptions() { - return LocalizationOptions( - 'zh', - notificationReportModeTitle: '发生应用错误', - notificationReportModeContent: '单击此处将错误报告发送给支持团队。', - dialogReportModeTitle: '错误', - dialogReportModeDescription: - '应用程序中发生意外错误。 错误报告已准备好发送给支持团队。 请单击“接受”以发送错误报告,或单击“取消”以关闭报告。', - dialogReportModeAccept: '接受', - dialogReportModeCancel: '取消', - pageReportModeTitle: '错误', - pageReportModeDescription: - '应用程序中发生意外错误。 错误报告已准备好发送给支持团队。 请单击“接受”以发送错误报告,或单击“取消”以关闭报告。', - pageReportModeAccept: '接受', - pageReportModeCancel: '取消', - toastHandlerDescription: '发生了错误:', - snackbarHandlerDescription: '发生了错误:', - ); - } + factory LocalizationOptions.buildDefaultChineseOptions() => + LocalizationOptions( + 'zh', + notificationReportModeTitle: '发生应用错误', + notificationReportModeContent: '单击此处将错误报告发送给支持团队。', + dialogReportModeTitle: '错误', + dialogReportModeDescription: + '应用程序中发生意外错误。 错误报告已准备好发送给支持团队。 请单击“接受”以发送错误报告,或单击“取消”以关闭报告。', + dialogReportModeAccept: '接受', + dialogReportModeCancel: '取消', + pageReportModeTitle: '错误', + pageReportModeDescription: + '应用程序中发生意外错误。 错误报告已准备好发送给支持团队。 请单击“接受”以发送错误报告,或单击“取消”以关闭报告。', + pageReportModeAccept: '接受', + pageReportModeCancel: '取消', + toastHandlerDescription: '发生了错误:', + snackbarHandlerDescription: '发生了错误:', + ); - static LocalizationOptions buildDefaultHindiOptions() { - return LocalizationOptions( - 'hi', - notificationReportModeTitle: 'एप्लिकेशन त्रुटि हुई', - notificationReportModeContent: - 'समर्थन टीम को त्रुटि रिपोर्ट भेजने के लिए यहां क्लिक करें।.', - dialogReportModeTitle: 'दुर्घटना', - dialogReportModeDescription: - 'आवेदन में अप्रत्याशित त्रुटि हुई। त्रुटि रिपोर्ट समर्थन टीम को ' - 'भेजने के लिए तैयार है। कृपया त्रुटि रिपोर्ट भेजने के लिए ' - 'स्वीकार करें या रिपोर्ट को रद्द करने के लिए रद्द करें पर क्लिक ' - 'करें।', - dialogReportModeAccept: 'स्वीकार करना', - dialogReportModeCancel: 'रद्द करना', - pageReportModeTitle: 'दुर्घटना', - pageReportModeDescription: - 'आवेदन में अप्रत्याशित त्रुटि हुई। त्रुटि रिपोर्ट समर्थन टीम को ' - 'भेजने के लिए तैयार है। कृपया त्रुटि रिपोर्ट भेजने के लिए ' - 'स्वीकार करें या रिपोर्ट को रद्द करने के लिए रद्द करें पर क्लिक ' - 'करें।', - pageReportModeAccept: 'स्वीकार करना', - pageReportModeCancel: 'रद्द करना', - toastHandlerDescription: 'त्रुटि हुई:', - snackbarHandlerDescription: 'त्रुटि हुई:', - ); - } + factory LocalizationOptions.buildDefaultHindiOptions() => LocalizationOptions( + 'hi', + notificationReportModeTitle: 'एप्लिकेशन त्रुटि हुई', + notificationReportModeContent: + 'समर्थन टीम को त्रुटि रिपोर्ट भेजने के लिए यहां क्लिक करें।.', + dialogReportModeTitle: 'दुर्घटना', + dialogReportModeDescription: + 'आवेदन में अप्रत्याशित त्रुटि हुई। त्रुटि रिपोर्ट समर्थन टीम को ' + 'भेजने के लिए तैयार है। कृपया त्रुटि रिपोर्ट भेजने के लिए स्वीकार ' + 'करें या रिपोर्ट को रद्द करने के लिए रद्द करें पर क्लिक करें।', + dialogReportModeAccept: 'स्वीकार करना', + dialogReportModeCancel: 'रद्द करना', + pageReportModeTitle: 'दुर्घटना', + pageReportModeDescription: + 'आवेदन में अप्रत्याशित त्रुटि हुई। त्रुटि रिपोर्ट समर्थन टीम को ' + 'भेजने के लिए तैयार है। कृपया त्रुटि रिपोर्ट भेजने के लिए स्वीकार ' + 'करें या रिपोर्ट को रद्द करने के लिए रद्द करें पर क्लिक करें।', + pageReportModeAccept: 'स्वीकार करना', + pageReportModeCancel: 'रद्द करना', + toastHandlerDescription: 'त्रुटि हुई:', + snackbarHandlerDescription: 'त्रुटि हुई:', + ); - static LocalizationOptions buildDefaultSpanishOptions() { - return LocalizationOptions( - 'es', - notificationReportModeTitle: 'Error de aplicación ocurrió', - notificationReportModeContent: - 'Haga clic aquí para enviar un informe de error al equipo de ' - 'soporte.', - dialogReportModeTitle: 'Choque', - dialogReportModeDescription: - 'Se ha producido un error inesperado en la aplicación. El informe ' - 'de errores está listo para enviar al equipo de soporte. ' - 'Haga clic en Aceptar para enviar el informe de errores o en ' - 'Cancelar para cancelar el informe.', - dialogReportModeAccept: 'Aceptar', - dialogReportModeCancel: 'Cancelar', - pageReportModeTitle: 'Choque', - pageReportModeDescription: - 'Se ha producido un error inesperado en la aplicación. El informe ' - 'de errores está listo para enviar al equipo de soporte. Haga ' - 'clic en Aceptar para enviar el informe de errores o en ' - 'Cancelar para cancelar el informe.', - pageReportModeAccept: 'Aceptar', - pageReportModeCancel: 'Cancelar', - toastHandlerDescription: 'Se produjo un error:', - snackbarHandlerDescription: 'Se produjo un error:', - ); - } + factory LocalizationOptions.buildDefaultSpanishOptions() => + LocalizationOptions( + 'es', + notificationReportModeTitle: 'Error de aplicación ocurrió', + notificationReportModeContent: + 'Haga clic aquí para enviar un informe de error al equipo de ' + 'soporte.', + dialogReportModeTitle: 'Choque', + dialogReportModeDescription: + 'Se ha producido un error inesperado en la aplicación. El informe ' + 'de errores está listo para enviar al equipo de soporte. Haga clic ' + 'en Aceptar para enviar el informe de errores o en Cancelar para ' + 'cancelar el informe.', + dialogReportModeAccept: 'Aceptar', + dialogReportModeCancel: 'Cancelar', + pageReportModeTitle: 'Choque', + pageReportModeDescription: + 'Se ha producido un error inesperado en la aplicación. El informe ' + 'de errores está listo para enviar al equipo de soporte. Haga clic ' + 'en Aceptar para enviar el informe de errores o en Cancelar para ' + 'cancelar el informe.', + pageReportModeAccept: 'Aceptar', + pageReportModeCancel: 'Cancelar', + toastHandlerDescription: 'Se produjo un error:', + snackbarHandlerDescription: 'Se produjo un error:', + ); - static LocalizationOptions buildDefaultMalayOptions() { - return LocalizationOptions( - 'ms', - notificationReportModeTitle: 'Ralat permohonan berlaku', - notificationReportModeContent: - 'Klik di sini untuk menghantar laporan ralat untuk menyokong ' - 'pasukan.', - dialogReportModeTitle: 'Kemalangan', - dialogReportModeDescription: - 'Ralat tidak dijangka berlaku dalam aplikasi. Laporan ralat sedia ' - 'dihantar untuk menyokong pasukan. Sila klik Terima untuk ' - 'menghantar laporan ralat atau Batal untuk menolak laporan.', - dialogReportModeAccept: 'Terima', - dialogReportModeCancel: 'Batalkan', - pageReportModeTitle: 'Kemalangan', - pageReportModeDescription: - 'Ralat tidak dijangka berlaku dalam aplikasi. Laporan ralat sedia' - ' dihantar untuk menyokong pasukan. Sila klik Terima untuk ' - 'menghantar laporan ralat atau Batal untuk menolak laporan.', - pageReportModeAccept: 'Terima', - pageReportModeCancel: 'Batalkan', - toastHandlerDescription: 'Ralat berlaku:', - snackbarHandlerDescription: 'Ralat berlaku:', - ); - } + factory LocalizationOptions.buildDefaultMalayOptions() => LocalizationOptions( + 'ms', + notificationReportModeTitle: 'Ralat permohonan berlaku', + notificationReportModeContent: + 'Klik di sini untuk menghantar laporan ralat untuk menyokong ' + 'pasukan.', + dialogReportModeTitle: 'Kemalangan', + dialogReportModeDescription: + 'Ralat tidak dijangka berlaku dalam aplikasi. Laporan ralat sedia ' + 'dihantar untuk menyokong pasukan. Sila klik Terima untuk ' + 'menghantar laporan ralat atau Batal untuk menolak laporan.', + dialogReportModeAccept: 'Terima', + dialogReportModeCancel: 'Batalkan', + pageReportModeTitle: 'Kemalangan', + pageReportModeDescription: + 'Ralat tidak dijangka berlaku dalam aplikasi. Laporan ralat sedia ' + 'dihantar untuk menyokong pasukan. Sila klik Terima untuk ' + 'menghantar laporan ralat atau Batal untuk menolak laporan.', + pageReportModeAccept: 'Terima', + pageReportModeCancel: 'Batalkan', + toastHandlerDescription: 'Ralat berlaku:', + snackbarHandlerDescription: 'Ralat berlaku:', + ); - static LocalizationOptions buildDefaultRussianOptions() { - return LocalizationOptions( - 'ru', - notificationReportModeTitle: 'Произошла ошибка приложения', - notificationReportModeContent: - 'Нажмите здесь, чтобы отправить отчет об ошибке в службу поддержки.', - dialogReportModeTitle: 'авария', - dialogReportModeDescription: - 'В приложении произошла непредвиденная ошибка. Отчет об ошибке готов ' - 'к отправке в службу поддержки. Пожалуйста, нажмите Принять, чтобы ' - 'отправить отчет об ошибке или Отмена, чтобы закрыть отчет.', - dialogReportModeAccept: 'принимать', - dialogReportModeCancel: 'отменить', - pageReportModeTitle: 'авария', - pageReportModeDescription: - 'В приложении произошла непредвиденная ошибка. Отчет об ошибке готов ' - 'к отправке в службу поддержки. Пожалуйста, нажмите Принять, чтобы ' - 'отправить отчет об ошибке или Отмена, чтобы закрыть отчет.', - pageReportModeAccept: 'принимать', - pageReportModeCancel: 'отменить', - toastHandlerDescription: 'Произошла ошибка:', - snackbarHandlerDescription: 'Произошла ошибка:', - ); - } + factory LocalizationOptions.buildDefaultRussianOptions() => + LocalizationOptions( + 'ru', + notificationReportModeTitle: 'В приложении произошла ошибка', + notificationReportModeContent: + 'Нажмите здесь, чтобы отправить отчет об ошибке в службу ' + 'поддержки.', + dialogReportModeTitle: 'Сбой', + dialogReportModeDescription: + 'В приложении произошла непредвиденная ошибка. Отчет об ошибке ' + 'готов к отправке в службу поддержки. Пожалуйста, нажмите ' + 'Принять, чтобы отправить отчет об ошибке или Отмена, чтобы ' + 'закрыть отчет.', + dialogReportModeAccept: 'Принять', + dialogReportModeCancel: 'Отмена', + pageReportModeTitle: 'Сбой', + pageReportModeDescription: + 'В приложении произошла непредвиденная ошибка. Отчет об ошибке ' + 'готов к отправке в службу поддержки. Пожалуйста, нажмите ' + 'Принять, чтобы отправить отчет об ошибке или Отмена, чтобы ' + 'закрыть отчет.', + pageReportModeAccept: 'Принять', + pageReportModeCancel: 'Отмена', + toastHandlerDescription: 'Произошла ошибка:', + snackbarHandlerDescription: 'Произошла ошибка:', + ); - static LocalizationOptions buildDefaultPortugueseOptions() { - return LocalizationOptions( - 'pt', - notificationReportModeTitle: 'Erro na aplicação', - notificationReportModeContent: - 'Clique aqui para enviar o relatório de erros à equipe de suporte.', - dialogReportModeTitle: 'Erro', - dialogReportModeDescription: - 'Ocorreu um erro inesperado no aplicativo. O relatório de erros está ' - 'pronto para ser enviado à equipe de suporte. Por favor, clique em ' - 'Aceitar para enviar o relatório de erros ou em Cancelar para ' - 'descartar o relatório.', - dialogReportModeAccept: 'Aceitar', - dialogReportModeCancel: 'Cancelar', - pageReportModeTitle: 'Erro', - pageReportModeDescription: - 'Ocorreu um erro inesperado no aplicativo. O relatório de erros ' - 'está pronto para ser enviado à equipe de suporte. Por favor, clique ' - 'em Aceitar para enviar o relatório de erros ou em Cancelar para ' - 'descartar o relatório.', - pageReportModeAccept: 'Aceitar', - pageReportModeCancel: 'Cancelar', - toastHandlerDescription: 'Ocorreu um erro:', - snackbarHandlerDescription: 'Ocorreu um erro:', - ); - } + factory LocalizationOptions.buildDefaultPortugueseOptions() => + LocalizationOptions( + 'pt', + notificationReportModeTitle: 'Erro na aplicação', + notificationReportModeContent: + 'Clique aqui para enviar o relatório de erros à equipe de suporte.', + dialogReportModeTitle: 'Erro', + dialogReportModeDescription: + 'Ocorreu um erro inesperado no aplicativo. O relatório de erros ' + 'está pronto para ser enviado à equipe de suporte. Por favor, ' + 'clique em Aceitar para enviar o relatório de erros ou em Cancelar ' + 'para descartar o relatório.', + dialogReportModeAccept: 'Aceitar', + dialogReportModeCancel: 'Cancelar', + pageReportModeTitle: 'Erro', + pageReportModeDescription: + 'Ocorreu um erro inesperado no aplicativo. O relatório de erros ' + 'está pronto para ser enviado à equipe de suporte. Por favor, ' + 'clique em Aceitar para enviar o relatório de erros ou em Cancelar ' + 'para descartar o relatório.', + pageReportModeAccept: 'Aceitar', + pageReportModeCancel: 'Cancelar', + toastHandlerDescription: 'Ocorreu um erro:', + snackbarHandlerDescription: 'Ocorreu um erro:', + ); - static LocalizationOptions buildDefaultFrenchOptions() { - return LocalizationOptions( - 'fr', - notificationReportModeTitle: "Une erreur d'application s'est produite", - notificationReportModeContent: - "Cliquez ici pour envoyer un rapport d'erreur à l'équipe de support.", - dialogReportModeTitle: 'Fracas', - dialogReportModeDescription: - "Une erreur inattendue s'est produite dans l'application. Le rapport " - "d'erreur est prêt à être envoyé à l'équipe de support. Cliquez sur " - "Accepter pour envoyer le rapport d'erreur ou sur Annuler pour" - ' rejeter le rapport.', - dialogReportModeAccept: 'Acceptez', - dialogReportModeCancel: 'Annuler', - pageReportModeTitle: 'Fracas', - pageReportModeDescription: - "Une erreur inattendue s'est produite dans l'application. Le rapport " - "d'erreur est prêt à être envoyé à l'équipe de support. Cliquez sur " - "Accepter pour envoyer le rapport d'erreur ou sur Annuler pour " - 'rejeter le rapport.', - pageReportModeAccept: 'Acceptez', - pageReportModeCancel: 'Annuler', - toastHandlerDescription: 'Erreur est survenue:', - snackbarHandlerDescription: 'Erreur est survenue:', - ); - } + factory LocalizationOptions.buildDefaultFrenchOptions() => + LocalizationOptions( + 'fr', + notificationReportModeTitle: "Une erreur d'application s'est produite", + notificationReportModeContent: + "Cliquez ici pour envoyer un rapport d'erreur à l'équipe de " + 'support.', + dialogReportModeTitle: 'Fracas', + dialogReportModeDescription: + "Une erreur inattendue s'est produite dans l'application. " + "Le rapport d'erreur est prêt à être envoyé à l'équipe de support. " + "Cliquez sur Accepter pour envoyer le rapport d'erreur ou sur " + 'Annuler pour rejeter le rapport.', + dialogReportModeAccept: 'Acceptez', + dialogReportModeCancel: 'Annuler', + pageReportModeTitle: 'Fracas', + pageReportModeDescription: + "Une erreur inattendue s'est produite dans l'application. " + "Le rapport d'erreur est prêt à être envoyé à l'équipe de support. " + "Cliquez sur Accepter pour envoyer le rapport d'erreur ou sur " + 'Annuler pour rejeter le rapport.', + pageReportModeAccept: 'Acceptez', + pageReportModeCancel: 'Annuler', + toastHandlerDescription: 'Erreur est survenue:', + snackbarHandlerDescription: 'Erreur est survenue:', + ); - static LocalizationOptions buildDefaultPolishOptions() { - return LocalizationOptions( - 'pl', - notificationReportModeTitle: 'Wystąpił błąd aplikacji', - notificationReportModeContent: - 'Naciśnij tutaj aby wysłać raport do zespołu wpsarcia', - dialogReportModeTitle: 'Błąd aplikacji', - dialogReportModeDescription: - 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest gotowy ' - 'do wysłania do zespołu wsparcia. Naciśnij akceptuj aby wysłać' - ' raport lub odrzuć aby odrzucić raport.', - dialogReportModeAccept: 'Akceptuj', - dialogReportModeCancel: 'Odrzuć', - pageReportModeTitle: 'Błąd aplikacji', - pageReportModeDescription: - 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest ' - 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby ' - 'wysłać raport lub odrzuć aby odrzucić raport.', - pageReportModeAccept: 'Akceptuj', - pageReportModeCancel: 'Odrzuć', - toastHandlerDescription: 'Wystąpił błąd:', - snackbarHandlerDescription: 'Wystąpił błąd:', - ); - } + factory LocalizationOptions.buildDefaultPolishOptions() => + LocalizationOptions( + 'pl', + notificationReportModeTitle: 'Wystąpił błąd aplikacji', + notificationReportModeContent: + 'Naciśnij tutaj aby wysłać raport do zespołu wpsarcia', + dialogReportModeTitle: 'Błąd aplikacji', + dialogReportModeDescription: + 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest ' + 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby ' + 'wysłać raport lub odrzuć aby odrzucić raport.', + dialogReportModeAccept: 'Akceptuj', + dialogReportModeCancel: 'Odrzuć', + pageReportModeTitle: 'Błąd aplikacji', + pageReportModeDescription: + 'Wystąpił niespodziewany błąd aplikacji. Raport z błędem jest ' + 'gotowy do wysłania do zespołu wsparcia. Naciśnij akceptuj aby ' + 'wysłać raport lub odrzuć aby odrzucić raport.', + pageReportModeAccept: 'Akceptuj', + pageReportModeCancel: 'Odrzuć', + toastHandlerDescription: 'Wystąpił błąd:', + snackbarHandlerDescription: 'Wystąpił błąd:', + ); - static LocalizationOptions buildDefaultItalianOptions() { - return LocalizationOptions( - 'it', - notificationReportModeTitle: 'Si è verificato un errore', - notificationReportModeContent: - "Clicca qui per inviare il report relativo all'errore al team di " - 'support.', - dialogReportModeTitle: 'Errore', - dialogReportModeDescription: - "Si è verificato un errore imprevisto durante l'esecuzione. " - 'Il report è pronto per essere inviato al team di supporto. ' - 'Clicca Acetate per inviare il report or Annulla per rifiutare.', - dialogReportModeAccept: 'Accetta', - dialogReportModeCancel: 'Annulla', - pageReportModeTitle: 'Errore', - pageReportModeDescription: - "Si è verificato un errore imprevisto durante l'esecuzione. " - 'Il report è pronto per essere inviato al team di supporto. ' - 'Clicca Acetate per inviare il report or Annulla per rifiutare.', - pageReportModeAccept: 'Accetta', - pageReportModeCancel: 'Annulla', - toastHandlerDescription: 'Errore:', - snackbarHandlerDescription: 'Errore:', - ); - } + factory LocalizationOptions.buildDefaultItalianOptions() => + LocalizationOptions( + 'it', + notificationReportModeTitle: 'Si è verificato un errore', + notificationReportModeContent: + "Clicca qui per inviare il report relativo all'errore al team " + 'di supporto.', + dialogReportModeTitle: 'Errore', + dialogReportModeDescription: + "Si è verificato un errore imprevisto durante l'esecuzione. Il " + 'report è pronto per essere inviato al team di supporto. ' + 'Clicca Accetta per inviare il report or Annulla per rifiutare.', + dialogReportModeAccept: 'Accetta', + dialogReportModeCancel: 'Annulla', + pageReportModeTitle: 'Errore', + pageReportModeDescription: + "Si è verificato un errore imprevisto durante l'esecuzione. Il " + 'report è pronto per essere inviato al team di supporto. ' + 'Clicca Accetta per inviare il report or Annulla per rifiutare.', + pageReportModeAccept: 'Accetta', + pageReportModeCancel: 'Annulla', + toastHandlerDescription: 'Errore:', + snackbarHandlerDescription: 'Errore:', + ); - static LocalizationOptions buildDefaultKoreanOptions() { - return LocalizationOptions( - 'ko', - notificationReportModeTitle: '어플리케이션 에러 발생', - notificationReportModeContent: '지원팀에 오류를 보고하시려면 여기를 클릭하세요', - dialogReportModeTitle: '에러', - dialogReportModeDescription: - '어플리케이션에서 예기치 않은 오류가 발생했습니다. 지원팀에 오류를 보고할 준비가 되어' - ' 있으니 수락을 클릭하여 오류 보고서를 전송하시거나 취소를 클릭하여 보고서를 닫으세요.', - dialogReportModeAccept: '수락', - dialogReportModeCancel: '취소', - pageReportModeTitle: '에러', - pageReportModeDescription: '어플리케이션에서 예기치 않은 오류가 발생했습니다. 지원팀에 오류를 보고할 ' - '준비가 되어 있으니 수락을 클릭하여 오류 보고서를 전송하시거나 취소를 클릭하여 보고서를 닫으세요.', - pageReportModeAccept: '수락', - pageReportModeCancel: '취소', - toastHandlerDescription: '오류가 발생했습니다:', - snackbarHandlerDescription: '오류가 발생했습니다:', - ); - } + factory LocalizationOptions.buildDefaultKoreanOptions() => + LocalizationOptions( + 'ko', + notificationReportModeTitle: '어플리케이션 에러 발생', + notificationReportModeContent: '지원팀에 오류를 보고하시려면 여기를 클릭하세요', + dialogReportModeTitle: '에러', + dialogReportModeDescription: + '어플리케이션에서 예기치 않은 오류가 발생했습니다. 지원팀에 오류를 보고할 준비가 되어 있으니 수락을 클릭하여 오류' + ' 보고서를 전송하시거나 취소를 클릭하여 보고서를 닫으세요.', + dialogReportModeAccept: '수락', + dialogReportModeCancel: '취소', + pageReportModeTitle: '에러', + pageReportModeDescription: + '어플리케이션에서 예기치 않은 오류가 발생했습니다. 지원팀에 오류를 보고할 준비가 되어 있으니 수락을 클릭하여 ' + '오류 보고서를 전송하시거나 취소를 클릭하여 보고서를 닫으세요.', + pageReportModeAccept: '수락', + pageReportModeCancel: '취소', + toastHandlerDescription: '오류가 발생했습니다:', + snackbarHandlerDescription: '오류가 발생했습니다:', + ); - static LocalizationOptions buildDefaultDutchOptions() { - return LocalizationOptions( - 'nl', - notificationReportModeTitle: 'Er is een fout opgetreden', - notificationReportModeContent: - 'Klik hier om het foutrapport te versturen naar het ' - 'ondersteuningsteam.', - dialogReportModeTitle: 'Error', - dialogReportModeDescription: - 'Er is een onverwachte fout opgetreden in de applicatie. ' - 'Het foutrapport is klaar om naar het ondersteuningsteam te ' - 'worden verstuurd. Druk op accepteer om het rapport te versturen ' - 'of op annuleer om het rapport te verwijderen.', - dialogReportModeAccept: 'Accepteer', - dialogReportModeCancel: 'Annuleer', - pageReportModeTitle: 'Error', - pageReportModeDescription: - 'Er is een onverwachte fout opgetreden in de applicatie. Het ' - 'foutrapport is klaar om naar het ondersteuningsteam te worden ' - 'verstuurd. Druk op accepteer om het rapport te versturen of op ' - 'annuleer om het rapport te verwijderen.', - pageReportModeAccept: 'Accepteer', - pageReportModeCancel: 'Annuleer', - toastHandlerDescription: 'Der er sket en fejl:', - snackbarHandlerDescription: 'Der er sket en fejl:', - ); - } + factory LocalizationOptions.buildDefaultDutchOptions() => LocalizationOptions( + 'nl', + notificationReportModeTitle: 'Er is een fout opgetreden', + notificationReportModeContent: + 'Klik hier om het foutrapport te versturen naar het ' + 'ondersteuningsteam.', + dialogReportModeTitle: 'Error', + dialogReportModeDescription: + 'Er is een onverwachte fout opgetreden in de applicatie. Het ' + 'foutrapport is klaar om naar het ondersteuningsteam te worden ' + 'verstuurd. Druk op accepteer om het rapport te versturen of op ' + 'annuleer om het rapport te verwijderen.', + dialogReportModeAccept: 'Accepteer', + dialogReportModeCancel: 'Annuleer', + pageReportModeTitle: 'Error', + pageReportModeDescription: + 'Er is een onverwachte fout opgetreden in de applicatie. Het ' + 'foutrapport is klaar om naar het ondersteuningsteam te worden ' + 'verstuurd. Druk op accepteer om het rapport te versturen of op ' + 'annuleer om het rapport te verwijderen.', + pageReportModeAccept: 'Accepteer', + pageReportModeCancel: 'Annuleer', + toastHandlerDescription: 'Der er sket en fejl:', + snackbarHandlerDescription: 'Der er sket en fejl:', + ); - static LocalizationOptions buildDefaultGermanOptions() { - return LocalizationOptions( - 'de', - notificationReportModeTitle: 'Ein Anwendungsfehler ist aufgetreten', - notificationReportModeContent: - 'Klicken Sie hier, um einen Fehlerbericht an das Support-Team zu ' - 'senden.', - dialogReportModeTitle: 'Absturz', - dialogReportModeDescription: - 'Unerwarteter Fehler in der Anwendung aufgetreten. Der ' - 'Fehlerbericht ist bereit zum Senden an das Support-Team. ' - 'Bitte klicken Sie auf Akzeptieren, um den Fehlerbericht zu ' - 'senden, oder auf Abbrechen, um den Bericht zu verwerfen.', - dialogReportModeAccept: 'Akzeptieren', - dialogReportModeCancel: 'Abbrechen', - pageReportModeTitle: 'Absturz', - pageReportModeDescription: - 'Unerwarteter Fehler in der Anwendung aufgetreten. Der Fehlerbericht' - ' ist bereit zum Senden an das Support-Team. Bitte klicken Sie ' - 'auf Akzeptieren, um den Fehlerbericht zu senden, oder auf Abbrechen,' - ' um den Bericht zu verwerfen.', - pageReportModeAccept: 'Akzeptieren', - pageReportModeCancel: 'Abbrechen', - toastHandlerDescription: 'Es ist ein Fehler aufgetreten:', - snackbarHandlerDescription: 'Es ist ein Fehler aufgetreten:', - ); - } + factory LocalizationOptions.buildDefaultGermanOptions() => + LocalizationOptions( + 'de', + notificationReportModeTitle: 'Ein Anwendungsfehler ist aufgetreten', + notificationReportModeContent: + 'Klicken Sie hier, um einen Fehlerbericht an das Support-Team zu ' + 'senden.', + dialogReportModeTitle: 'Absturz', + dialogReportModeDescription: + 'Unerwarteter Fehler in der Anwendung aufgetreten. ' + 'Der Fehlerbericht ist bereit zum Senden an das Support-Team. ' + 'Bitte klicken Sie auf Akzeptieren, um den Fehlerbericht zu ' + 'senden, oder auf Abbrechen, um den Bericht zu verwerfen.', + dialogReportModeAccept: 'Akzeptieren', + dialogReportModeCancel: 'Abbrechen', + pageReportModeTitle: 'Absturz', + pageReportModeDescription: + 'Unerwarteter Fehler in der Anwendung aufgetreten. ' + 'Der Fehlerbericht ist bereit zum Senden an das Support-Team. ' + 'Bitte klicken Sie auf Akzeptieren, um den Fehlerbericht zu ' + 'senden, oder auf Abbrechen, um den Bericht zu verwerfen.', + pageReportModeAccept: 'Akzeptieren', + pageReportModeCancel: 'Abbrechen', + toastHandlerDescription: 'Es ist ein Fehler aufgetreten:', + snackbarHandlerDescription: 'Es ist ein Fehler aufgetreten:', + ); + + factory LocalizationOptions.buildDefaultTurkishOptions() => + LocalizationOptions( + 'tr', + notificationReportModeTitle: 'Bir Uygulama Hatası Meydana Geldi', + notificationReportModeContent: + 'Destek ekibine hata raporu göndermek için buraya tıklayın.', + dialogReportModeTitle: 'Çökme', + dialogReportModeDescription: + 'Uygulamada beklenmeyen bir hata meydana geldi. ' + 'Hata raporu, destek ekibine gönderilmeye hazır. ' + "Hata raporunu göndermek için Kabul Et'e veya raporu reddetmek " + "için İptal'e tıklayın.", + dialogReportModeAccept: 'Kabul Et', + dialogReportModeCancel: 'İptal', + pageReportModeTitle: 'Çökme', + pageReportModeDescription: + 'Uygulamada beklenmeyen bir hata meydana geldi. Hata raporu ' + 'destek ekibine gönderilmeye hazır. Lütfen hata raporunu göndermek ' + "için Kabul Et'e tıklayın veya raporu reddetmek için İptal'e " + 'tıklayın.', + pageReportModeAccept: 'Kabul Et', + pageReportModeCancel: 'İptal', + toastHandlerDescription: 'Bir hata meydana geldi:', + snackbarHandlerDescription: 'Bir hata meydana geldi:', + ); + + final String languageCode; + final String notificationReportModeTitle; + final String notificationReportModeContent; + + final String dialogReportModeTitle; + final String dialogReportModeDescription; + final String dialogReportModeAccept; + final String dialogReportModeCancel; + + final String pageReportModeTitle; + final String pageReportModeDescription; + final String pageReportModeAccept; + final String pageReportModeCancel; + + final String toastHandlerDescription; + final String snackbarHandlerDescription; - ///Helper method used to copy values of current LocalizationOptions with new - ///values passed in method. + /// Helper method used to copy values of current [LocalizationOptions] with + /// new values passed in method. LocalizationOptions copyWith({ String? languageCode, String? notificationReportModeTitle, @@ -367,30 +404,29 @@ class LocalizationOptions { String? pageReportModeCancel, String? toastHandlerDescription, String? snackbarHandlerDescription, - }) { - return LocalizationOptions( - languageCode ?? this.languageCode, - notificationReportModeTitle: - notificationReportModeTitle ?? this.notificationReportModeTitle, - notificationReportModeContent: - notificationReportModeContent ?? this.notificationReportModeContent, - dialogReportModeTitle: - dialogReportModeTitle ?? this.dialogReportModeTitle, - dialogReportModeDescription: - dialogReportModeDescription ?? this.dialogReportModeDescription, - dialogReportModeAccept: - dialogReportModeAccept ?? this.dialogReportModeAccept, - dialogReportModeCancel: - dialogReportModeCancel ?? this.dialogReportModeCancel, - pageReportModeTitle: pageReportModeTitle ?? this.pageReportModeTitle, - pageReportModeDescription: - pageReportModeDescription ?? this.pageReportModeDescription, - pageReportModeAccept: pageReportModeAccept ?? this.pageReportModeAccept, - pageReportModeCancel: pageReportModeCancel ?? this.pageReportModeCancel, - toastHandlerDescription: - toastHandlerDescription ?? this.toastHandlerDescription, - snackbarHandlerDescription: - snackbarHandlerDescription ?? this.snackbarHandlerDescription, - ); - } + }) => + LocalizationOptions( + languageCode ?? this.languageCode, + notificationReportModeTitle: + notificationReportModeTitle ?? this.notificationReportModeTitle, + notificationReportModeContent: + notificationReportModeContent ?? this.notificationReportModeContent, + dialogReportModeTitle: + dialogReportModeTitle ?? this.dialogReportModeTitle, + dialogReportModeDescription: + dialogReportModeDescription ?? this.dialogReportModeDescription, + dialogReportModeAccept: + dialogReportModeAccept ?? this.dialogReportModeAccept, + dialogReportModeCancel: + dialogReportModeCancel ?? this.dialogReportModeCancel, + pageReportModeTitle: pageReportModeTitle ?? this.pageReportModeTitle, + pageReportModeDescription: + pageReportModeDescription ?? this.pageReportModeDescription, + pageReportModeAccept: pageReportModeAccept ?? this.pageReportModeAccept, + pageReportModeCancel: pageReportModeCancel ?? this.pageReportModeCancel, + toastHandlerDescription: + toastHandlerDescription ?? this.toastHandlerDescription, + snackbarHandlerDescription: + snackbarHandlerDescription ?? this.snackbarHandlerDescription, + ); } diff --git a/lib/model/report.dart b/lib/model/report.dart index 28e8abe9..26847770 100644 --- a/lib/model/report.dart +++ b/lib/model/report.dart @@ -1,9 +1,21 @@ -import 'dart:io'; - -import 'package:catcher/model/platform_type.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:flutter/foundation.dart'; class Report { + /// Creates report instance + Report( + this.error, + this.stackTrace, + this.dateTime, + this.deviceParameters, + this.applicationParameters, + this.customParameters, + this.errorDetails, + this.platformType, + this.screenshot, + ); + /// Error that has been caught final dynamic error; @@ -28,22 +40,9 @@ class Report { /// Type of platform used final PlatformType platformType; - ///Screenshot of screen where error happens. Screenshot won't work everywhere - /// (i.e. web platform), so this may be null. - final File? screenshot; - - /// Creates report instance - Report( - this.error, - this.stackTrace, - this.dateTime, - this.deviceParameters, - this.applicationParameters, - this.customParameters, - this.errorDetails, - this.platformType, - this.screenshot, - ); + /// Screenshot of screen where error happens. Screenshot won't work everywhere + /// (i.e. web platform), so this may be `null` + final XFile? screenshot; /// Creates json from current instance Map toJson({ diff --git a/lib/model/report_handler.dart b/lib/model/report_handler.dart index 265ee196..2284c80c 100644 --- a/lib/model/report_handler.dart +++ b/lib/model/report_handler.dart @@ -1,12 +1,15 @@ -import 'package:catcher/model/localization_options.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; -import 'package:catcher/utils/catcher_logger.dart'; +import 'package:catcher_2/model/localization_options.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; +import 'package:catcher_2/utils/catcher_2_logger.dart'; import 'package:flutter/material.dart'; abstract class ReportHandler { - ///Logger instance - late CatcherLogger logger; + /// Logger instance + late Catcher2Logger logger; + + /// Localization settings + LocalizationOptions? _localizationOptions; /// Method called when report has been accepted by user Future handle(Report report, BuildContext? context); @@ -14,26 +17,22 @@ abstract class ReportHandler { /// Get list of supported platforms List getSupportedPlatforms(); - ///Location settings - LocalizationOptions? _localizationOptions; - /// Get currently used localization options LocalizationOptions get localizationOptions => _localizationOptions ?? LocalizationOptions.buildDefaultEnglishOptions(); - // ignore: use_setters_to_change_properties /// Set localization options (translations) to this report mode - void setLocalizationOptions(LocalizationOptions? localizationOptions) { + set localizationOptions(LocalizationOptions? localizationOptions) { _localizationOptions = localizationOptions; } /// Check if given report mode requires context to run - bool isContextRequired() { - return false; - } + bool isContextRequired() => false; /// Check whether report mode should auto confirm without user confirmation. - bool shouldHandleWhenRejected() { - return false; - } + bool shouldHandleWhenRejected() => false; + + /// Can be overridden to provide more information about this handler. + @override + String toString() => runtimeType.toString(); } diff --git a/lib/model/report_mode.dart b/lib/model/report_mode.dart index b40c01a6..18ea0cab 100644 --- a/lib/model/report_mode.dart +++ b/lib/model/report_mode.dart @@ -1,22 +1,21 @@ -import 'package:catcher/mode/report_mode_action_confirmed.dart'; -import 'package:catcher/model/localization_options.dart'; -import 'package:catcher/model/platform_type.dart'; -import 'package:catcher/model/report.dart'; +import 'package:catcher_2/mode/report_mode_action_confirmed.dart'; +import 'package:catcher_2/model/localization_options.dart'; +import 'package:catcher_2/model/platform_type.dart'; +import 'package:catcher_2/model/report.dart'; import 'package:flutter/widgets.dart'; abstract class ReportMode { late ReportModeAction _reportModeAction; LocalizationOptions? _localizationOptions; - // ignore: use_setters_to_change_properties /// Set report mode action. - void setReportModeAction(ReportModeAction reportModeAction) { + // ignore: avoid_setters_without_getters + set reportModeAction(ReportModeAction reportModeAction) { _reportModeAction = reportModeAction; } - // ignore: use_setters_to_change_properties /// Set localization options (translations) to this report mode - void setLocalizationOptions(LocalizationOptions? localizationOptions) { + set localizationOptions(LocalizationOptions? localizationOptions) { _localizationOptions = localizationOptions; } @@ -35,9 +34,7 @@ abstract class ReportMode { } /// Check if given report mode requires context to run - bool isContextRequired() { - return false; - } + bool isContextRequired() => false; /// Get currently used localization options LocalizationOptions get localizationOptions => diff --git a/lib/utils/catcher_2_error_widget.dart b/lib/utils/catcher_2_error_widget.dart new file mode 100644 index 00000000..eb95e8e9 --- /dev/null +++ b/lib/utils/catcher_2_error_widget.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; + +class Catcher2ErrorWidget extends StatelessWidget { + const Catcher2ErrorWidget({ + super.key, + this.details, + required this.showStacktrace, + required this.title, + required this.description, + required this.maxWidthForSmallMode, + }) : assert( + maxWidthForSmallMode > 0, + 'maxWidthForSmallMode must be positive', + ); + final FlutterErrorDetails? details; + final bool showStacktrace; + final String title; + final String description; + final double maxWidthForSmallMode; + + @override + Widget build(BuildContext context) => LayoutBuilder( + builder: (context, constraint) => + constraint.maxWidth < maxWidthForSmallMode + ? _buildSmallErrorWidget(context) + : _buildNormalErrorWidget(context), + ); + + Widget _buildSmallErrorWidget(BuildContext context) => Center( + child: Icon( + Icons.error_outline, + color: Theme.of(context).colorScheme.error, + size: 40, + ), + ); + + Widget _buildNormalErrorWidget(BuildContext context) => Container( + margin: const EdgeInsets.all(20), + child: Center( + child: ListView( + children: [ + _buildIcon(context), + Text( + title, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(fontSize: 25) ?? + const TextStyle(fontSize: 25), + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + Text( + _getDescription(), + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + _buildStackTraceWidget(), + ], + ), + ), + ); + + Widget _buildIcon(BuildContext context) => Icon( + Icons.announcement, + color: Theme.of(context).colorScheme.error, + size: 40, + ); + + Widget _buildStackTraceWidget() { + if (showStacktrace) { + final items = []; + if (details != null) { + items + ..add(details!.exception.toString()) + ..addAll(details!.stack.toString().split('\n')); + } + return ListView.builder( + padding: const EdgeInsets.all(8), + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: items.length, + itemBuilder: (context, index) { + final line = items[index]; + return line.isNotEmpty ? Text(line) : const SizedBox(); + }, + ); + } else { + return const SizedBox(); + } + } + + String _getDescription() { + var descriptionText = description; + if (showStacktrace) { + descriptionText += ' See details below.'; + } + return descriptionText; + } +} diff --git a/lib/utils/catcher_2_logger.dart b/lib/utils/catcher_2_logger.dart new file mode 100644 index 00000000..af51aa6b --- /dev/null +++ b/lib/utils/catcher_2_logger.dart @@ -0,0 +1,32 @@ +import 'package:logging/logging.dart'; + +/// Class used to provide logger for Catcher 2. +class Catcher2Logger { + final Logger _logger = Logger('Catcher 2'); + + /// Setup logger configuration. + void setup() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen( + (rec) { + // ignore: avoid_print + print( + '[${rec.time} | ${rec.loggerName} | ${rec.level.name}] ' + '${rec.message}', + ); + }, + ); + } + + /// Log info message. + void info(String message) => _logger.info(message); + + /// Log fine message. + void fine(String message) => _logger.fine(message); + + /// Log warning message. + void warning(String message) => _logger.warning(message); + + /// Log severe message. + void severe(String message) => _logger.severe(message); +} diff --git a/lib/utils/catcher_2_utils.dart b/lib/utils/catcher_2_utils.dart new file mode 100644 index 00000000..89ba12bd --- /dev/null +++ b/lib/utils/catcher_2_utils.dart @@ -0,0 +1,27 @@ +import 'package:catcher_2/core/application_profile_manager.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:universal_io/io.dart'; + +class Catcher2Utils { + /// From https://stackoverflow.com/a/56959146/5894824 + static Future isInternetConnectionAvailable() async { + if (ApplicationProfileManager.isWeb()) { + return true; // TODO(HyperSpeeed): We could in theory handle this maybe? + } else { + try { + final result = await InternetAddress.lookup('google.com'); + return result.isNotEmpty && result[0].rawAddress.isNotEmpty; + } catch (_) {} + } + return false; + } + + static bool isCupertinoAppAncestor(BuildContext context) => + context.findAncestorWidgetOfExactType() != null; +} + +/// From https://stackoverflow.com/a/70282800/5894824 +extension IsOk on Response { + bool get ok => statusCode != null && (statusCode! ~/ 100) == 2; +} diff --git a/lib/utils/catcher_error_widget.dart b/lib/utils/catcher_error_widget.dart deleted file mode 100644 index 32dcb168..00000000 --- a/lib/utils/catcher_error_widget.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; - -class CatcherErrorWidget extends StatelessWidget { - final FlutterErrorDetails? details; - final bool showStacktrace; - final String title; - final String description; - final double maxWidthForSmallMode; - - const CatcherErrorWidget({ - required this.showStacktrace, - required this.title, - required this.description, - required this.maxWidthForSmallMode, - super.key, - this.details, - }) : assert( - maxWidthForSmallMode > 0, - 'Max width for small mode must be greater than 0', - ); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraint) { - if (constraint.maxWidth < maxWidthForSmallMode) { - return _buildSmallErrorWidget(); - } else { - return _buildNormalErrorWidget(); - } - }, - ); - } - - Widget _buildSmallErrorWidget() { - return const Center( - child: Icon( - Icons.error_outline, - color: Colors.red, - size: 40, - ), - ); - } - - Widget _buildNormalErrorWidget() { - return Container( - margin: const EdgeInsets.all(20), - child: Center( - child: ListView( - children: [ - _buildIcon(), - Text( - title, - style: const TextStyle(color: Colors.black, fontSize: 25), - textAlign: TextAlign.center, - ), - const SizedBox(height: 10), - Text( - _getDescription(), - textAlign: TextAlign.center, - ), - const SizedBox(height: 10), - _buildStackTraceWidget(), - ], - ), - ), - ); - } - - Widget _buildIcon() { - return const Icon( - Icons.announcement, - color: Colors.red, - size: 40, - ); - } - - Widget _buildStackTraceWidget() { - if (showStacktrace) { - final items = []; - if (details != null) { - items - ..add(details!.exception.toString()) - ..addAll(details!.stack.toString().split('\n')); - } - return ListView.builder( - padding: const EdgeInsets.all(8), - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - itemCount: items.length, - itemBuilder: (BuildContext context, int index) { - final line = items[index]; - if (line.isNotEmpty == true) { - return Text(line); - } else { - return const SizedBox(); - } - }, - ); - } else { - return const SizedBox(); - } - } - - String _getDescription() { - var descriptionText = description; - if (showStacktrace) { - descriptionText += ' See details below.'; - } - return descriptionText; - } -} diff --git a/lib/utils/catcher_logger.dart b/lib/utils/catcher_logger.dart deleted file mode 100644 index 73c03503..00000000 --- a/lib/utils/catcher_logger.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:logging/logging.dart'; - -///Class used to provide logger for Catcher. -class CatcherLogger { - final Logger _logger = Logger('Catcher'); - - ///Setup logger configuration. - void setup() { - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen( - (LogRecord rec) { - // ignore: avoid_print - print( - '[${rec.time} | ${rec.loggerName} | ${rec.level.name}] ' - '${rec.message}', - ); - }, - ); - } - - ///Log info message. - void info(String message) { - _logger.info(message); - } - - ///Log fine message. - void fine(String message) { - _logger.fine(message); - } - - ///Log warning message. - void warning(String message) { - _logger.warning(message); - } - - ///Log severe mesasge. - void severe(String message) { - _logger.severe(message); - } -} diff --git a/lib/utils/catcher_utils.dart b/lib/utils/catcher_utils.dart deleted file mode 100644 index 0b6eebb4..00000000 --- a/lib/utils/catcher_utils.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; - -class CatcherUtils { - static Future isInternetConnectionAvailable() async { - try { - final result = await InternetAddress.lookup('google.com'); - if (result.isNotEmpty == true && result[0].rawAddress.isNotEmpty) { - return Future.value(true); - } else { - return Future.value(false); - } - } catch (exception) { - return Future.value(false); - } - } - - static bool isCupertinoAppAncestor(BuildContext context) { - return context.findAncestorWidgetOfExactType() != null; - } -} diff --git a/platform_support.md b/platform_support.md index b4f8cfa6..642fd961 100644 --- a/platform_support.md +++ b/platform_support.md @@ -1,10 +1,10 @@ ## Platform support -Catcher supports all platforms available in Flutter: -* Mobile: Android & iOS +Catcher 2 supports all platforms available in Flutter: +* Mobile: Android, iOS * Web * Desktop: Linux, Windows, MacOS -Some features available in Android/iOS won't be available in other platforms due to no support of dependencies used by Catcher. This may change in the future, but right now some features are disabled in Web/Desktop platforms. +Some features available in Android/iOS won't be available in other platforms due to no support of dependencies used by Catcher 2. This may change in the future, but right now some features are disabled in Web/Desktop platforms. Check table below to see which features are enabled in Platforms: | Feature | Android | iOS | Web | Linux | Windows | MacOS | diff --git a/pubspec.yaml b/pubspec.yaml index 87bac65f..c1b6e163 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,36 +1,55 @@ -name: catcher +name: catcher_2 description: Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer. -version: 1.0.0-dev1 -#author: Jakub Homlala -homepage: https://github.com/jhomlala/catcher -repository: https://github.com/jhomlala/catcher -issue_tracker: https://github.com/jhomlala/catcher/issues +version: 2.1.0 +homepage: https://github.com/ThexXTURBOXx/catcher_2 +repository: https://github.com/ThexXTURBOXx/catcher_2 +issue_tracker: https://github.com/ThexXTURBOXx/catcher_2/issues + +funding: + - https://github.com/sponsors/ThexXTURBOXx + +screenshots: + - description: 'The catcher_2 logo' + path: screenshots/logo.png + - description: 'catcher_2 in action with notifications' + path: screenshots/1.png + - description: 'catcher_2 in action with emails' + path: screenshots/3.png + - description: 'catcher_2 in action with dialogs' + path: screenshots/6.png + topics: - - error + - analysis - errors - - logs - - devtools + - flutter + - logging - tool environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + sdk: '>=3.0.0 <4.0.0' + flutter: ">=3.0.0" dependencies: + cross_file: ^0.3.0 + device_info_plus: '>=9.0.0 <12.0.0' + dio: ^5.0.1 flutter: sdk: flutter - flutter_web_plugins: - sdk: flutter + flutter_mailer: ^2.0.0 fluttertoast: ^8.2.6 - device_info_plus: ^10.1.0 - device_info_plus_platform_interface: ^7.0.0 - package_info_plus: ^8.0.0 - mailer: ^6.1.0 - dio: ^5.4.3+1 - flutter_mailer: ^2.1.2 - logging: ^1.2.0 - sentry: ^8.2.0 - universal_io: ^2.2.2 + logging: ^1.0.2 + mailer: ^6.0.0 + package_info_plus: '>=4.0.0 <9.0.0' + sentry: '>=7.2.0 <9.0.0' + universal_io: ^2.0.4 dev_dependencies: - very_good_analysis: ^5.1.0 + flutter_lints: ^5.0.0 + +platforms: + android: + ios: + linux: + macos: + web: + windows: