From cb213a709415c32b4b4528793d863c3449bc60b2 Mon Sep 17 00:00:00 2001 From: Jakub Homlala <1329033+jhomlala@users.noreply.github.com> Date: Sun, 7 Jul 2024 13:14:03 +0200 Subject: [PATCH] refactor: Codebase refactor part 2 & bug fixes (#221) * feat: introduced AliceConfiguration * feat: introduced AliceConfiguration * fix: fixed invalid count of http calls in notification * feat: set configuration optional * feat: added payload to notification * fix: format * fix: fixed tests * fix: dart format * feat: updated docs * feat: updated metadata * feat: updated tests * refactor: refactored logger * refactor: refactored codebase * refactor: refactored codebase * refactor: refactored codebase * refactor: refactored codebase * fix: fixed PR comments * fix: fixed PR comments, updated metadata * feat: updated metadata * feat: dart fix --- docs/config.md | 50 ++++++--- docs/install.md | 19 +++- docs/other.md | 8 -- docs/usage.md | 9 -- examples/alice/lib/main.dart | 7 +- examples/alice_chopper/lib/main.dart | 9 +- examples/alice_dio/lib/main.dart | 9 +- examples/alice_http/lib/main.dart | 9 +- examples/alice_http_client/lib/main.dart | 9 +- examples/alice_objectbox/lib/main.dart | 11 +- packages/alice/CHANGELOG.md | 13 +++ packages/alice/README.md | 2 + packages/alice/lib/alice.dart | 63 +---------- packages/alice/lib/core/alice_adapter.dart | 1 + packages/alice/lib/core/alice_core.dart | 104 +++++++----------- packages/alice/lib/core/alice_logger.dart | 75 ++++--------- .../alice/lib/core/alice_memory_storage.dart | 45 +++++--- .../alice/lib/core/alice_notification.dart | 20 ++-- packages/alice/lib/core/alice_storage.dart | 11 ++ packages/alice/lib/core/alice_store.dart | 3 - .../alice/lib/core/alice_translations.dart | 11 +- packages/alice/lib/core/alice_utils.dart | 1 + .../lib/helper/alice_conversion_helper.dart | 24 ++-- .../alice/lib/helper/alice_export_helper.dart | 5 +- .../alice/lib/helper/operating_system.dart | 6 + .../alice/lib/model/alice_configuration.dart | 87 +++++++++++++++ packages/alice/lib/model/alice_http_call.dart | 12 +- .../alice/lib/model/alice_translation.dart | 1 + .../page/alice_call_details_page.dart | 25 +++-- .../widget/alice_call_response_screen.dart | 28 ++++- .../page/alice_calls_list_page.dart | 9 +- .../widget/alice_calls_list_screen.dart | 1 + .../widget/alice_error_logs_widget.dart | 34 ++++++ .../widget/alice_inspector_screen.dart | 2 + .../widget/alice_log_list_widget.dart | 28 +++-- .../calls_list/widget/alice_logs_screen.dart | 4 +- .../widget/alice_raw_log_list_widger.dart | 66 ++++++----- .../lib/ui/common/alice_context_ext.dart | 3 + .../alice/lib/ui/common/alice_navigation.dart | 4 +- packages/alice/lib/ui/common/alice_page.dart | 3 +- packages/alice/lib/utils/alice_parser.dart | 2 + packages/alice/lib/utils/curl.dart | 97 ++++++++-------- packages/alice/lib/utils/shake_detector.dart | 8 +- packages/alice/pubspec.yaml | 2 +- packages/alice/test/alice_test.dart | 84 ++++++++++++++ packages/alice/test/core/alice_core_test.dart | 19 ++-- .../alice/test/core/alice_logger_test.dart | 15 --- .../test/core/alice_memory_storage_test.dart | 8 +- .../test/helper/alice_export_helper_test.dart | 2 +- .../alice/test/{ => mock}/mocked_data.dart | 0 packages/alice_dio/pubspec.yaml | 2 +- packages/alice_objectbox/CHANGELOG.md | 3 + .../alice_objectbox/lib/alice_objectbox.dart | 11 +- .../lib/alice_objectbox_store.dart | 6 +- .../lib/model/cached_alice_http_call.dart | 6 - packages/alice_objectbox/pubspec.yaml | 2 +- 56 files changed, 649 insertions(+), 449 deletions(-) delete mode 100644 packages/alice/lib/core/alice_store.dart create mode 100644 packages/alice/lib/model/alice_configuration.dart create mode 100644 packages/alice/lib/ui/calls_list/widget/alice_error_logs_widget.dart create mode 100644 packages/alice/test/alice_test.dart rename packages/alice/test/{ => mock}/mocked_data.dart (100%) diff --git a/docs/config.md b/docs/config.md index 8216ece3..91d08d89 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,59 +1,75 @@ ## Configuration + 1. Create Alice instance: ```dart + Alice alice = Alice(); ``` 2. Add navigator key to your application: ```dart -MaterialApp( navigatorKey: alice.getNavigatorKey(), home: ...) +MaterialApp(navigatorKey: alice.getNavigatorKey(), home: ...); ``` You need to add this navigator key in order to show inspector UI. You can use also your navigator key in Alice: ```dart -Alice alice = Alice(showNotification: true, navigatorKey: yourNavigatorKeyHere); + +Alice alice = Alice(configuration: AliceConfiguration(navigatorKey: yourNavigatorKeyHere)); ``` If you need to pass navigatorKey lazily, you can use: + ```dart alice.setNavigatorKey(yourNavigatorKeyHere); ``` -This is minimal configuration required to run Alice. Can set optional settings in Alice constructor, which are presented below. If you don't want to change anything, you can move to Http clients configuration. -### Additional settings +This is minimal configuration required to run Alice. Can set optional settings in Alice constructor, +which are presented below. If you don't want to change anything, you can move to Http clients +configuration. -You can set `showNotification` in Alice constructor to show notification. Clicking on this notification will open inspector. -```dart -Alice alice = Alice(..., showNotification: true); -``` +### Alice configuration -You can set `showInspectorOnShake` in Alice constructor to open inspector by shaking your device (default disabled): +You can pass optional `AliceConfiguration` parameter to `Alice` instance. + +You can set `showNotification` in Alice constructor to show notification. Clicking on this +notification will open inspector. ```dart -Alice alice = Alice(..., showInspectorOnShake: true); + +Alice alice = Alice(configuration: AliceConfiguration(showNotification: true)); ``` -If you want to pass another notification icon, you can use `notificationIcon` parameter. Default value is @mipmap/ic_launcher. +You can set `showInspectorOnShake` in Alice constructor to open inspector by shaking your device ( +default disabled): + ```dart -Alice alice = Alice(..., notificationIcon: "myNotificationIconResourceName"); + +Alice alice = Alice(configuration: AliceConfiguation(showInspectorOnShake: true)); ``` -If you want to limit max numbers of HTTP calls saved in memory, you may use `maxCallsCount` parameter. +If you want to pass another notification icon, you can use `notificationIcon` parameter. Default +value is @mipmap/ic_launcher. ```dart -Alice alice = Alice(..., maxCallsCount: 1000)); + +Alice alice = Alice(configuration: AliceConfiguration(notificationIcon: "myNotificationIconResourceName")); ``` -If you want to change the Directionality of Alice, you can use the `directionality` parameter. If the parameter is set to null, the Directionality of the app will be used. +If you want to change the Directionality of Alice, you can use the `directionality` parameter. If +the parameter is set to null, the Directionality of the app will be used. + ```dart -Alice alice = Alice(..., directionality: TextDirection.ltr); + +Alice alice = Alice(configuration: AliceConfiguration(directionality: TextDirection.ltr)); ``` If you want to hide share button, you can use `showShareButton` parameter. + ```dart -Alice alice = Alice(..., showShareButton: false); + +Alice alice = Alice(configuration: AliceConfiguration(showShareButton: false)); ``` \ No newline at end of file diff --git a/docs/install.md b/docs/install.md index 005dca88..0a76f374 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,36 +4,51 @@ ```yaml dependencies: - alice: ^1.0.0-dev9 + alice: ^1.0.0-dev10 ``` 2. Choose adapter based on your HTTP client. **pubspec.yaml** file: ### Dio + ```yaml dependencies: alice_dio: ^1.0.4 ``` ### Chopper + ```yaml dependencies: alice_chopper: ^1.0.5 ``` ### Http + ```yaml dependencies: alice_http: ^1.0.4 ``` ### Http Client + ```yaml dependencies: alice_http_client: ^1.0.4 ``` -3. Run `get` command: +3. Choose optional database: + +### Objectbox + +```yaml +dependencies: + objectbox: any + alice_objectbox: ^1.0.2 +``` + +4. Run `get` command: + ```bash $ flutter packages get ``` \ No newline at end of file diff --git a/docs/other.md b/docs/other.md index 72d565ab..cb480550 100644 --- a/docs/other.md +++ b/docs/other.md @@ -8,14 +8,6 @@ You may need that if you won't use shake or notification: alice.showInspector(); ``` -## Saving calls - -Alice supports saving logs to your mobile device storage. In order to make save feature works, you need to add in your Android application manifest: - -```xml - -``` - ## Flutter logs If you want to log Flutter logs in Alice, you may use these methods: diff --git a/docs/usage.md b/docs/usage.md index e9c10743..9f95ca27 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -105,15 +105,6 @@ httpClient Setting up ObjectBox with Alice is simple, however, there are a few crucial steps which need to be followed. -1. Add it to your dependencies - -```yaml -dependencies: - alice_objectbox: ^1.0.0 -``` - -2. Follow the ObjectBox [example](https://github.com/objectbox/objectbox-dart/blob/main/objectbox/example/flutter/objectbox_demo/lib/main.dart) - ```dart Future main() async { /// This is required so ObjectBox can get the application directory diff --git a/examples/alice/lib/main.dart b/examples/alice/lib/main.dart index 5f483389..6d2ddf3a 100644 --- a/examples/alice/lib/main.dart +++ b/examples/alice/lib/main.dart @@ -1,4 +1,5 @@ import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); @@ -11,11 +12,7 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - ); + late final Alice _alice = Alice(configuration: AliceConfiguration()); @override Widget build(BuildContext context) { diff --git a/examples/alice_chopper/lib/main.dart b/examples/alice_chopper/lib/main.dart index 20ef81c4..fce75b50 100644 --- a/examples/alice_chopper/lib/main.dart +++ b/examples/alice_chopper/lib/main.dart @@ -1,4 +1,5 @@ import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice_chopper/alice_chopper_adapter.dart'; import 'package:alice_chopper_example/interceptors/json_content_type_inerceptor.dart'; import 'package:alice_chopper_example/interceptors/json_headers_interceptor.dart'; @@ -35,11 +36,9 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final AliceChopperAdapter _aliceChopperAdapter = AliceChopperAdapter(); - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - )..addAdapter(_aliceChopperAdapter); + final configuration = AliceConfiguration(); + late final Alice _alice = Alice(configuration: configuration) + ..addAdapter(_aliceChopperAdapter); late final ChopperClient _chopper = ChopperClient( baseUrl: Uri.https('jsonplaceholder.typicode.com'), diff --git a/examples/alice_dio/lib/main.dart b/examples/alice_dio/lib/main.dart index c1913b67..d85883b2 100644 --- a/examples/alice_dio/lib/main.dart +++ b/examples/alice_dio/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice_dio/alice_dio_adapter.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; @@ -19,11 +20,9 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { late final AliceDioAdapter _aliceDioAdapter = AliceDioAdapter(); - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - )..addAdapter(_aliceDioAdapter); + final configuration = AliceConfiguration(); + late final Alice _alice = Alice(configuration: configuration) + ..addAdapter(_aliceDioAdapter); late final Dio _dio = Dio(BaseOptions(followRedirects: false)) ..interceptors.add(_aliceDioAdapter); diff --git a/examples/alice_http/lib/main.dart b/examples/alice_http/lib/main.dart index 3059cc06..249f6421 100644 --- a/examples/alice_http/lib/main.dart +++ b/examples/alice_http/lib/main.dart @@ -1,4 +1,5 @@ import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice_http/alice_http_adapter.dart'; import 'package:alice_http/alice_http_extensions.dart'; import 'package:flutter/material.dart'; @@ -16,11 +17,9 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { late final AliceHttpAdapter _aliceHttpAdapter = AliceHttpAdapter(); - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - )..addAdapter(_aliceHttpAdapter); + final configuration = AliceConfiguration(); + late final Alice _alice = Alice(configuration: configuration) + ..addAdapter(_aliceHttpAdapter); @override Widget build(BuildContext context) { diff --git a/examples/alice_http_client/lib/main.dart b/examples/alice_http_client/lib/main.dart index ee35496c..0302e699 100644 --- a/examples/alice_http_client/lib/main.dart +++ b/examples/alice_http_client/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice_http_client/alice_http_client_adapter.dart'; import 'package:alice_http_client/alice_http_client_extensions.dart'; import 'package:flutter/material.dart'; @@ -21,11 +22,9 @@ class _MyAppState extends State { late final AliceHttpClientAdapter _httpClientAdapter = AliceHttpClientAdapter(); - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - )..addAdapter(_httpClientAdapter); + final configuration = AliceConfiguration(); + late final Alice _alice = Alice(configuration: configuration) + ..addAdapter(_httpClientAdapter); @override Widget build(BuildContext context) { diff --git a/examples/alice_objectbox/lib/main.dart b/examples/alice_objectbox/lib/main.dart index b3662888..efbf2013 100644 --- a/examples/alice_objectbox/lib/main.dart +++ b/examples/alice_objectbox/lib/main.dart @@ -1,4 +1,5 @@ import 'package:alice/alice.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice_http/alice_http_adapter.dart'; import 'package:alice_http/alice_http_extensions.dart'; import 'package:alice_objectbox/alice_objectbox.dart'; @@ -34,14 +35,14 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { late final AliceHttpAdapter _aliceHttpAdapter = AliceHttpAdapter(); - late final Alice _alice = Alice( - showNotification: true, - showInspectorOnShake: true, - maxCallsCount: 1000, - aliceStorage: AliceObjectBox( + late final configuration = AliceConfiguration( + storage: AliceObjectBox( store: widget.store, maxCallsCount: 1000, ), + ); + late final Alice _alice = Alice( + configuration: configuration )..addAdapter(_aliceHttpAdapter); @override diff --git a/packages/alice/CHANGELOG.md b/packages/alice/CHANGELOG.md index 73fb580f..5bb93eed 100644 --- a/packages/alice/CHANGELOG.md +++ b/packages/alice/CHANGELOG.md @@ -1,4 +1,15 @@ +# 1.0.0-dev.10 + +* [BREAKING_CHANGE] Removed `maxCallsCount`. To change call count, use `storage` constructor parameter + with call count. +* [BREAKING_CHANGE] Replaced configuration parameter of Alice with `AliceConfiguration`. +* Fixed issue with invalid count of calls in notification. +* Added payload to notification. +* General refactor of code base. +* Updated documentation. + # 1.0.0-dev.9 + * Fixed saving issue with Android 13 onwards. * Added unit tests. * Updated CI/CD task for tests. @@ -6,11 +17,13 @@ * Updated metadata. # 1.0.0-dev.8 + * Added storage abstractions (by Klemen Tusar https://github.com/techouse). * Added in memory storage implementation (by Klemen Tusar https://github.com/techouse). * Added translations. # 1.0.0-dev.7 + * Refactored UI code. # 1.0.0-dev.6 diff --git a/packages/alice/README.md b/packages/alice/README.md index 1e1fa832..a8d7a123 100644 --- a/packages/alice/README.md +++ b/packages/alice/README.md @@ -77,6 +77,8 @@ Alice is an HTTP Inspector tool for Flutter which helps debugging http requests. ✔️ Shake to open inspector ✔️ HTTP calls search ✔️ Flutter/Android logs +✔️ ObjectBox storage + ## Documentation You can find documentation [here.](https://jhomlala.github.io/alice/) \ No newline at end of file diff --git a/packages/alice/lib/alice.dart b/packages/alice/lib/alice.dart index c24407d5..cea943f8 100644 --- a/packages/alice/lib/alice.dart +++ b/packages/alice/lib/alice.dart @@ -1,85 +1,34 @@ import 'package:alice/core/alice_adapter.dart'; import 'package:alice/core/alice_core.dart'; -import 'package:alice/core/alice_logger.dart'; -import 'package:alice/core/alice_memory_storage.dart'; -import 'package:alice/core/alice_storage.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice/model/alice_http_call.dart'; import 'package:alice/model/alice_log.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -export 'package:alice/core/alice_store.dart'; export 'package:alice/model/alice_log.dart'; export 'package:alice/core/alice_memory_storage.dart'; export 'package:alice/utils/alice_parser.dart'; class Alice { - /// Should user be notified with notification when there's new request caught - /// by Alice - final bool showNotification; - - /// Should inspector be opened on device shake (works only with physical - /// with sensors) - final bool showInspectorOnShake; - - /// Icon url for notification - final String notificationIcon; - - /// Max number of calls that are stored in memory. When count is reached, FIFO - /// method queue will be used to remove elements. - final int maxCallsCount; - - /// Directionality of app. Directionality of the app will be used if set to - /// null. - final TextDirection? directionality; - - /// Flag used to show/hide share button - final bool? showShareButton; - /// Alice core instance late final AliceCore _aliceCore; - /// Alice storage instance - final AliceStorage? _aliceStorage; - - /// Navigator key used for navigating to inspector - GlobalKey? _navigatorKey; - /// Creates alice instance. - Alice({ - GlobalKey? navigatorKey, - this.showNotification = true, - this.showInspectorOnShake = false, - this.notificationIcon = '@mipmap/ic_launcher', - this.maxCallsCount = 1000, - this.directionality, - this.showShareButton = true, - AliceStorage? aliceStorage, - }) : _navigatorKey = navigatorKey ?? GlobalKey(), - _aliceStorage = aliceStorage { + Alice({AliceConfiguration? configuration}) { _aliceCore = AliceCore( - _navigatorKey, - showNotification: showNotification, - showInspectorOnShake: showInspectorOnShake, - notificationIcon: notificationIcon, - directionality: directionality, - showShareButton: showShareButton, - aliceStorage: _aliceStorage ?? - AliceMemoryStorage( - maxCallsCount: maxCallsCount, - ), - aliceLogger: AliceLogger(), + configuration: configuration ?? AliceConfiguration(), ); } /// Set custom navigation key. This will help if there's route library. void setNavigatorKey(GlobalKey navigatorKey) { - _navigatorKey = navigatorKey; - _aliceCore.navigatorKey = navigatorKey; + _aliceCore.setNavigatorKey(navigatorKey); } /// Get currently used navigation key - GlobalKey? getNavigatorKey() => _navigatorKey; + GlobalKey? getNavigatorKey() => + _aliceCore.configuration.navigatorKey; /// Opens Http calls inspector. This will navigate user to the new fullscreen /// page where all listened http calls can be viewed. diff --git a/packages/alice/lib/core/alice_adapter.dart b/packages/alice/lib/core/alice_adapter.dart index d0f5604c..c1116a51 100644 --- a/packages/alice/lib/core/alice_adapter.dart +++ b/packages/alice/lib/core/alice_adapter.dart @@ -4,5 +4,6 @@ import 'package:alice/core/alice_core.dart'; mixin AliceAdapter { late final AliceCore aliceCore; + /// Injects [AliceCore] into adapter. void injectCore(AliceCore aliceCore) => this.aliceCore = aliceCore; } diff --git a/packages/alice/lib/core/alice_core.dart b/packages/alice/lib/core/alice_core.dart index ce5519b3..51b2473d 100644 --- a/packages/alice/lib/core/alice_core.dart +++ b/packages/alice/lib/core/alice_core.dart @@ -1,11 +1,11 @@ import 'dart:async' show FutureOr, StreamSubscription; -import 'package:alice/core/alice_logger.dart'; import 'package:alice/core/alice_storage.dart'; import 'package:alice/core/alice_utils.dart'; import 'package:alice/helper/alice_export_helper.dart'; import 'package:alice/core/alice_notification.dart'; import 'package:alice/helper/operating_system.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice/model/alice_export_result.dart'; import 'package:alice/model/alice_http_call.dart'; import 'package:alice/model/alice_http_error.dart'; @@ -15,37 +15,9 @@ import 'package:alice/ui/common/alice_navigation.dart'; import 'package:alice/utils/shake_detector.dart'; import 'package:flutter/material.dart'; -typedef AliceOnCallsChanged = Future Function(List? calls); - class AliceCore { - /// Should user be notified with notification if there's new request caught - /// by Alice - final bool showNotification; - - /// Should inspector be opened on device shake (works only with physical - /// with sensors) - final bool showInspectorOnShake; - - /// Icon url for notification - final String notificationIcon; - - /// Storage used for Alice to keep calls data. - final AliceStorage _aliceStorage; - - /// Logger used for Alice to keep logs; - final AliceLogger _aliceLogger; - - ///Directionality of app. If null then directionality of context will be used. - final TextDirection? directionality; - - ///Flag used to show/hide share button - final bool? showShareButton; - - /// Navigator key used for inspector navigator. - GlobalKey? navigatorKey; - - /// Flag used to determine whether is inspector opened - bool _isInspectorOpened = false; + /// Configuration of Alice + late AliceConfiguration _configuration; /// Detector used to detect device shakes ShakeDetector? _shakeDetector; @@ -56,27 +28,21 @@ class AliceCore { /// Subscription for call changes StreamSubscription>? _callsSubscription; + /// Flag used to determine whether is inspector opened + bool _isInspectorOpened = false; + /// Creates alice core instance - AliceCore( - this.navigatorKey, { - required this.showNotification, - required this.showInspectorOnShake, - required this.notificationIcon, - required AliceStorage aliceStorage, - required AliceLogger aliceLogger, - this.directionality, - this.showShareButton, - }) : _aliceStorage = aliceStorage, - _aliceLogger = aliceLogger { + AliceCore({required AliceConfiguration configuration}) { + _configuration = configuration; _subscribeToCallChanges(); - if (showNotification) { + if (_configuration.showNotification) { _notification = AliceNotification(); _notification?.configure( - notificationIcon: notificationIcon, + notificationIcon: _configuration.notificationIcon, openInspectorCallback: navigateToCallListScreen, ); } - if (showInspectorOnShake) { + if (_configuration.showInspectorOnShake) { if (OperatingSystem.isAndroid || OperatingSystem.isMacOS) { _shakeDetector = ShakeDetector.autoStart( onPhoneShake: navigateToCallListScreen, @@ -86,6 +52,14 @@ class AliceCore { } } + /// Returns current configuration + AliceConfiguration get configuration => _configuration; + + /// Set custom navigation key. This will help if there's route library. + void setNavigatorKey(GlobalKey navigatorKey) { + _configuration = _configuration.copyWith(navigatorKey: navigatorKey); + } + /// Dispose subjects and subscriptions void dispose() { _shakeDetector?.stopListening(); @@ -95,7 +69,7 @@ class AliceCore { /// Called when calls has been updated Future _onCallsChanged(List? calls) async { if (calls != null && calls.isNotEmpty) { - final AliceStats stats = _aliceStorage.getStats(); + final AliceStats stats = _configuration.aliceStorage.getStats(); _notification?.showStatsNotification( context: getContext()!, stats: stats, @@ -105,7 +79,7 @@ class AliceCore { /// Opens Http calls inspector. This will navigate user to the new fullscreen /// page where all listened http calls can be viewed. - void navigateToCallListScreen() { + Future navigateToCallListScreen() async { final BuildContext? context = getContext(); if (context == null) { AliceUtils.log( @@ -116,56 +90,60 @@ class AliceCore { } if (!_isInspectorOpened) { _isInspectorOpened = true; - AliceNavigation.navigateToCallsList(core: this, logger: _aliceLogger) - .then((_) => _isInspectorOpened = false); + await AliceNavigation.navigateToCallsList(core: this); + _isInspectorOpened = false; } } /// Get context from navigator key. Used to open inspector route. - BuildContext? getContext() => navigatorKey?.currentState?.overlay?.context; + BuildContext? getContext() => + _configuration.navigatorKey?.currentState?.overlay?.context; /// Add alice http call to calls subject - FutureOr addCall(AliceHttpCall call) => _aliceStorage.addCall(call); + FutureOr addCall(AliceHttpCall call) => + _configuration.aliceStorage.addCall(call); /// Add error to existing alice http call FutureOr addError(AliceHttpError error, int requestId) => - _aliceStorage.addError(error, requestId); + _configuration.aliceStorage.addError(error, requestId); /// Add response to existing alice http call FutureOr addResponse(AliceHttpResponse response, int requestId) => - _aliceStorage.addResponse(response, requestId); + _configuration.aliceStorage.addResponse(response, requestId); /// Remove all calls from calls subject - FutureOr removeCalls() => _aliceStorage.removeCalls(); + FutureOr removeCalls() => _configuration.aliceStorage.removeCalls(); /// Selects call with given [requestId]. It may return null. @protected AliceHttpCall? selectCall(int requestId) => - _aliceStorage.selectCall(requestId); + _configuration.aliceStorage.selectCall(requestId); /// Returns stream which returns list of HTTP calls - Stream> get callsStream => _aliceStorage.callsStream; + Stream> get callsStream => + _configuration.aliceStorage.callsStream; /// Returns all stored HTTP calls. - List getCalls() => _aliceStorage.getCalls(); + List getCalls() => _configuration.aliceStorage.getCalls(); /// Save all calls to file. - Future saveCallsToFile(BuildContext context) { - return AliceExportHelper.saveCallsToFile(context, _aliceStorage.getCalls()); - } + Future saveCallsToFile(BuildContext context) => + AliceExportHelper.saveCallsToFile( + context, _configuration.aliceStorage.getCalls()); /// Adds new log to Alice logger. - void addLog(AliceLog log) => _aliceLogger.add(log); + void addLog(AliceLog log) => _configuration.aliceLogger.add(log); /// Adds list of logs to Alice logger - void addLogs(List logs) => _aliceLogger.addAll(logs); + void addLogs(List logs) => _configuration.aliceLogger.addAll(logs); /// Returns flag which determines whether inspector is opened bool get isInspectorOpened => _isInspectorOpened; /// Subscribes to storage for call changes. void _subscribeToCallChanges() { - _callsSubscription = _aliceStorage.callsStream.listen(_onCallsChanged); + _callsSubscription = + _configuration.aliceStorage.callsStream.listen(_onCallsChanged); } /// Unsubscribes storage for call changes. diff --git a/packages/alice/lib/core/alice_logger.dart b/packages/alice/lib/core/alice_logger.dart index e63f5010..3f01e5da 100644 --- a/packages/alice/lib/core/alice_logger.dart +++ b/packages/alice/lib/core/alice_logger.dart @@ -2,77 +2,48 @@ import 'dart:io' show Process, ProcessResult; import 'package:alice/helper/operating_system.dart'; import 'package:alice/model/alice_log.dart'; -import 'package:alice/utils/num_comparison.dart'; -import 'package:flutter/foundation.dart'; +import 'package:rxdart/rxdart.dart'; /// Logger used to handle logs from application. class AliceLogger { - AliceLogger({int? maximumSize = 1000}) : _maximumSize = maximumSize; + /// Maximum logs size. If 0, logs will be not rotated. + final int maximumSize; - final ValueNotifier> _logs = ValueNotifier>([]); + /// Subject which keeps logs. + final BehaviorSubject> _logsSubject; - ValueListenable> get listenable => _logs; + AliceLogger({required this.maximumSize}) + : _logsSubject = BehaviorSubject.seeded([]); - List get logs => listenable.value; + /// Getter of stream of logs + Stream> get logsStream => _logsSubject.stream; - int? _maximumSize; + /// Getter of all logs + List get logs => _logsSubject.value; - /// The maximum number of logs to store or `null` for unlimited storage. - /// - /// If more logs arrive, the oldest ones (based on their [ - /// AliceLog.timestamp]) will be removed. - int? get maximumSize => _maximumSize; - - set maximumSize(int? value) { - _maximumSize = maximumSize; - - if (value != null && logs.length > value) { - _logs.value = logs.sublist(logs.length - value, logs.length); - } - } - - void addAll(List logs) { + /// Adds all logs. + void addAll(Iterable logs) { for (var log in logs) { add(log); } } + /// Add one log. It sorts logs after adding new element. If [maximumSize] is + /// set and max size is reached, first log will be deleted. void add(AliceLog log) { - late final int index; - if (logs.isEmpty || !log.timestamp.isBefore(logs.last.timestamp)) { - // Quick path as new logs are usually more recent. - index = logs.length; - } else { - // Binary search to find the insertion index. - int min = 0; - int max = logs.length; - while (min < max) { - final int mid = min + ((max - min) >> 1); - final AliceLog item = logs[mid]; - if (log.timestamp.isBefore(item.timestamp)) { - max = mid; - } else { - min = mid + 1; - } - } - assert(min == max, ''); - index = min; + final values = _logsSubject.value; + final count = values.length; + if (maximumSize > 0 && count >= maximumSize) { + values.removeAt(0); } - int startIndex = 0; - if (maximumSize != null && logs.length.gte(maximumSize)) { - if (index == 0) return; - startIndex = logs.length - maximumSize! + 1; - } - _logs.value = [ - ...logs.sublist(startIndex, index), - log, - ...logs.sublist(index, logs.length), - ]; + values.add(log); + values.sort((log1, log2) => log1.timestamp.compareTo(log2.timestamp)); + _logsSubject.add(values); } /// Clears all logs. - void clearLogs() => _logs.value.clear(); + void clearLogs() => _logsSubject.add([]); /// Returns raw logs from Android via ADB. Future getAndroidRawLogs() async { diff --git a/packages/alice/lib/core/alice_memory_storage.dart b/packages/alice/lib/core/alice_memory_storage.dart index dffbfaaf..bd7c603f 100644 --- a/packages/alice/lib/core/alice_memory_storage.dart +++ b/packages/alice/lib/core/alice_memory_storage.dart @@ -9,23 +9,28 @@ import 'package:alice/utils/num_comparison.dart'; import 'package:collection/collection.dart'; import 'package:rxdart/subjects.dart'; +/// Storage which uses memory to store calls data. It's a default storage +/// method. class AliceMemoryStorage implements AliceStorage { AliceMemoryStorage({ required this.maxCallsCount, - }) : callsSubject = BehaviorSubject.seeded([]), + }) : _callsSubject = BehaviorSubject.seeded([]), assert(maxCallsCount > 0, 'Max calls count should be greater than 0'); - @override final int maxCallsCount; - final BehaviorSubject> callsSubject; + /// Subject which stores all HTTP calls. + final BehaviorSubject> _callsSubject; + /// Stream which returns all HTTP calls on change. @override - Stream> get callsStream => callsSubject.stream; + Stream> get callsStream => _callsSubject.stream; + /// Returns all HTTP calls. @override - List getCalls() => callsSubject.value; + List getCalls() => _callsSubject.value; + /// Returns stats based on calls. @override AliceStats getStats() { final List calls = getCalls(); @@ -45,30 +50,31 @@ class AliceMemoryStorage implements AliceStorage { (call.response?.status.lt(400) ?? false)) .length, errors: calls - .where( - (AliceHttpCall call) => - (call.response?.status.gte(400) ?? false) && - (call.response?.status.lt(600) ?? false), - ) + .where((AliceHttpCall call) => + ((call.response?.status.gte(400) ?? false) && + (call.response?.status.lt(600) ?? false)) || + const [-1, 0].contains(call.response?.status)) .length, loading: calls.where((AliceHttpCall call) => call.loading).length, ); } + /// Adds new call to calls list. @override void addCall(AliceHttpCall call) { - final int callsCount = callsSubject.value.length; + final int callsCount = _callsSubject.value.length; if (callsCount >= maxCallsCount) { - final List originalCalls = callsSubject.value; + final List originalCalls = _callsSubject.value; originalCalls.removeAt(0); originalCalls.add(call); - callsSubject.add(originalCalls); + _callsSubject.add(originalCalls); } else { - callsSubject.add([...callsSubject.value, call]); + _callsSubject.add([..._callsSubject.value, call]); } } + /// Adds error to a specific call. @override void addError(AliceHttpError error, int requestId) { final AliceHttpCall? selectedCall = selectCall(requestId); @@ -78,9 +84,10 @@ class AliceMemoryStorage implements AliceStorage { } selectedCall.error = error; - callsSubject.add([...callsSubject.value]); + _callsSubject.add([..._callsSubject.value]); } + /// Adds response to a specific call. @override void addResponse(AliceHttpResponse response, int requestId) { final AliceHttpCall? selectedCall = selectCall(requestId); @@ -95,13 +102,15 @@ class AliceMemoryStorage implements AliceStorage { ..duration = response.time.millisecondsSinceEpoch - (selectedCall.request?.time.millisecondsSinceEpoch ?? 0); - callsSubject.add([...callsSubject.value]); + _callsSubject.add([..._callsSubject.value]); } + /// Removes all calls. @override - void removeCalls() => callsSubject.add([]); + void removeCalls() => _callsSubject.add([]); + /// Searches for call with specific [requestId]. It may return null. @override - AliceHttpCall? selectCall(int requestId) => callsSubject.value + AliceHttpCall? selectCall(int requestId) => _callsSubject.value .firstWhereOrNull((AliceHttpCall call) => call.id == requestId); } diff --git a/packages/alice/lib/core/alice_notification.dart b/packages/alice/lib/core/alice_notification.dart index 41cbcfe4..555a0d0e 100644 --- a/packages/alice/lib/core/alice_notification.dart +++ b/packages/alice/lib/core/alice_notification.dart @@ -8,6 +8,10 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; /// Helper for displaying local notifications. class AliceNotification { + static const String _payload = 'Alice'; + static const String _channel = 'Alice'; + static const String _callCount = '[callCount]'; + /// Notification plugin instance FlutterLocalNotificationsPlugin? _flutterLocalNotificationsPlugin; @@ -32,9 +36,9 @@ class AliceNotification { _openInspectorCallback = openInspectorCallback; _notificationDetails = NotificationDetails( android: AndroidNotificationDetails( - 'Alice', - 'Alice', - channelDescription: 'Alice', + _channel, + _channel, + channelDescription: _channel, enableVibration: false, playSound: false, largeIcon: DrawableResourceAndroidBitmap(notificationIcon), @@ -99,8 +103,10 @@ class AliceNotification { } /// Formats [stats] for notification message. - String _getNotificationMessage( - {required BuildContext context, required AliceStats stats}) => + String _getNotificationMessage({ + required BuildContext context, + required AliceStats stats, + }) => [ if (stats.loading > 0) '${context.i18n(AliceTranslationKey.notificationLoading)} ${stats.loading}', @@ -136,10 +142,10 @@ class AliceNotification { 0, context .i18n(AliceTranslationKey.notificationTotalRequests) - .replaceAll("[requestCount]", stats.total.toString()), + .replaceAll(_callCount, stats.total.toString()), message, _notificationDetails, - payload: '', + payload: _payload, ); _notificationMessageDisplayed = message; diff --git a/packages/alice/lib/core/alice_storage.dart b/packages/alice/lib/core/alice_storage.dart index d1808882..0f7f7f31 100644 --- a/packages/alice/lib/core/alice_storage.dart +++ b/packages/alice/lib/core/alice_storage.dart @@ -4,6 +4,7 @@ import 'dart:async' show FutureOr; import 'package:alice/model/alice_http_error.dart'; import 'package:alice/model/alice_http_response.dart'; +/// Definition of call stats. typedef AliceStats = ({ int total, int successes, @@ -12,22 +13,32 @@ typedef AliceStats = ({ int loading, }); +/// Definition of storage abstract interface class AliceStorage { + /// Stream which returns all HTTP calls on change. abstract final Stream> callsStream; + /// Max calls number which should be stored. abstract final int maxCallsCount; + /// Returns all HTTP calls. List getCalls(); + /// Returns stats based on calls. AliceStats getStats(); + /// Searches for call with specific [requestId]. It may return null. AliceHttpCall? selectCall(int requestId); + /// Adds new call to calls list. FutureOr addCall(AliceHttpCall call); + /// Adds error to a specific call. FutureOr addError(AliceHttpError error, int requestId); + /// Adds response to a specific call. FutureOr addResponse(AliceHttpResponse response, int requestId); + /// Removes all calls. FutureOr removeCalls(); } diff --git a/packages/alice/lib/core/alice_store.dart b/packages/alice/lib/core/alice_store.dart deleted file mode 100644 index 1d97f0f9..00000000 --- a/packages/alice/lib/core/alice_store.dart +++ /dev/null @@ -1,3 +0,0 @@ -abstract interface class AliceStore { - void clear(); -} diff --git a/packages/alice/lib/core/alice_translations.dart b/packages/alice/lib/core/alice_translations.dart index 9b585146..a8391c1f 100644 --- a/packages/alice/lib/core/alice_translations.dart +++ b/packages/alice/lib/core/alice_translations.dart @@ -1,8 +1,11 @@ import 'package:alice/model/alice_translation.dart'; +/// Class used to manage translations in Alice. class AliceTranslations { + /// Contains list of translation data for all languages. static final List _translations = _initialise(); + /// Initialises translation data for all languages. static List _initialise() { List translations = []; translations.add(_buildEnTranslations()); @@ -10,6 +13,7 @@ class AliceTranslations { return translations; } + /// Builds [AliceTranslationData] for english language. static AliceTranslationData _buildEnTranslations() { return AliceTranslationData(languageCode: "en", values: { AliceTranslationKey.alice: "Alice", @@ -85,6 +89,7 @@ class AliceTranslations { AliceTranslationKey.callsListStats: "Stats", AliceTranslationKey.callsListSave: "Save", AliceTranslationKey.logsEmpty: "There are no logs to show", + AliceTranslationKey.logsError: "Failed to display error", AliceTranslationKey.logsItemError: "Error:", AliceTranslationKey.logsItemStackTrace: "Stack trace:", AliceTranslationKey.logsCopied: "Copied to clipboard.", @@ -121,7 +126,7 @@ class AliceTranslations { AliceTranslationKey.notificationRedirect: "Redirect:", AliceTranslationKey.notificationError: "Error:", AliceTranslationKey.notificationTotalRequests: - "Alice (total [requestCount] requests)", + "Alice (total [callCount] HTTP calls)", AliceTranslationKey.saveDialogPermissionErrorTitle: "Permission error", AliceTranslationKey.saveDialogPermissionErrorDescription: "Permission not granted. Couldn't save logs.", @@ -173,6 +178,7 @@ class AliceTranslations { }); } + /// Builds [AliceTranslationData] for polish language. static AliceTranslationData _buildPlTranslations() { return AliceTranslationData(languageCode: "pl", values: { AliceTranslationKey.alice: "Alice", @@ -249,6 +255,7 @@ class AliceTranslations { AliceTranslationKey.callsListStats: "Statystyki", AliceTranslationKey.callsListSave: "Zapis", AliceTranslationKey.logsEmpty: "Brak rezultatów", + AliceTranslationKey.logsError: "Problem z wyświetleniem logów.", AliceTranslationKey.logsItemError: "Błąd:", AliceTranslationKey.logsItemStackTrace: "Ślad stosu:", AliceTranslationKey.logsCopied: "Skopiowano do schowka.", @@ -285,7 +292,7 @@ class AliceTranslations { AliceTranslationKey.notificationRedirect: "Przekierowanie:", AliceTranslationKey.notificationError: "Błąd:", AliceTranslationKey.notificationTotalRequests: - "Alice (razem [requestCount] żądań)", + "Alice (razem [callCount] połączeń HTTP)", AliceTranslationKey.saveDialogPermissionErrorTitle: "Błąd pozwolenia", AliceTranslationKey.saveDialogPermissionErrorDescription: "Pozwolenie nieprzyznane. Nie można zapisać logów.", diff --git a/packages/alice/lib/core/alice_utils.dart b/packages/alice/lib/core/alice_utils.dart index ee15a3cf..74b1ef61 100644 --- a/packages/alice/lib/core/alice_utils.dart +++ b/packages/alice/lib/core/alice_utils.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart' show kReleaseMode, debugPrint; /// Utils used across multiple classes in app. class AliceUtils { + /// Logs debug text. static void log(String logMessage) { if (!kReleaseMode) { debugPrint(logMessage); diff --git a/packages/alice/lib/helper/alice_conversion_helper.dart b/packages/alice/lib/helper/alice_conversion_helper.dart index 0b80608c..1801993c 100644 --- a/packages/alice/lib/helper/alice_conversion_helper.dart +++ b/packages/alice/lib/helper/alice_conversion_helper.dart @@ -4,14 +4,20 @@ class AliceConversionHelper { static const int _megabyteAsByte = 1000000; static const int _secondAsMillisecond = 1000; static const int _minuteAsMillisecond = 60000; + static const String _bytes = "B"; + static const String _kiloBytes = "kB"; + static const String _megaBytes = "MB"; + static const String _milliseconds = "ms"; + static const String _seconds = "s"; + static const String _minutes = "min"; /// Format bytes text static String formatBytes(int bytes) => switch (bytes) { - int bytes when bytes < 0 => '-1 B', - int bytes when bytes <= _kilobyteAsByte => '$bytes B', + int bytes when bytes < 0 => '-1 $_bytes', + int bytes when bytes <= _kilobyteAsByte => '$bytes $_bytes', int bytes when bytes <= _megabyteAsByte => - '${_formatDouble(bytes / _kilobyteAsByte)} kB', - _ => '${_formatDouble(bytes / _megabyteAsByte)} MB', + '${_formatDouble(bytes / _kilobyteAsByte)} $_kiloBytes', + _ => '${_formatDouble(bytes / _megabyteAsByte)} $_megaBytes', }; /// Formats double with two numbers after dot. @@ -20,18 +26,18 @@ class AliceConversionHelper { /// Format time in milliseconds static String formatTime(int timeInMillis) { if (timeInMillis < 0) { - return '-1 ms'; + return '-1 $_milliseconds'; } if (timeInMillis <= _secondAsMillisecond) { - return '$timeInMillis ms'; + return '$timeInMillis $_milliseconds'; } if (timeInMillis <= _minuteAsMillisecond) { - return '${_formatDouble(timeInMillis / _secondAsMillisecond)} s'; + return '${_formatDouble(timeInMillis / _secondAsMillisecond)} $_seconds'; } final Duration duration = Duration(milliseconds: timeInMillis); - return '${duration.inMinutes} min ${duration.inSeconds.remainder(60)} s ' - '${duration.inMilliseconds.remainder(1000)} ms'; + return '${duration.inMinutes} $_minutes ${duration.inSeconds.remainder(60)} $_seconds ' + '${duration.inMilliseconds.remainder(1000)} $_milliseconds'; } } diff --git a/packages/alice/lib/helper/alice_export_helper.dart b/packages/alice/lib/helper/alice_export_helper.dart index 7e956335..5499937e 100644 --- a/packages/alice/lib/helper/alice_export_helper.dart +++ b/packages/alice/lib/helper/alice_export_helper.dart @@ -20,6 +20,7 @@ import 'package:share_plus/share_plus.dart'; class AliceExportHelper { static const JsonEncoder _encoder = JsonEncoder.withIndent(' '); + static const String _fileName = "alice_log"; /// Format log based on [call] and tries to share it. static Future shareCall({ @@ -97,7 +98,7 @@ class AliceExportHelper { final Directory externalDir = await getApplicationCacheDirectory(); final String fileName = - 'alice_log_${DateTime.now().millisecondsSinceEpoch}.txt'; + '${_fileName}_${DateTime.now().millisecondsSinceEpoch}.txt'; final File file = File('${externalDir.path}/$fileName')..createSync(); final IOSink sink = file.openWrite(mode: FileMode.append) ..write(await _buildAliceLog(context: context)); @@ -199,7 +200,7 @@ class AliceExportHelper { '--------------------------------------------\n', '${context.i18n(AliceTranslationKey.saveLogCurl)}\n', '--------------------------------------------\n', - getCurlCommand(call), + Curl.getCurlCommand(call), '\n', '==============================================\n', '\n', diff --git a/packages/alice/lib/helper/operating_system.dart b/packages/alice/lib/helper/operating_system.dart index 44b5941a..21200b61 100644 --- a/packages/alice/lib/helper/operating_system.dart +++ b/packages/alice/lib/helper/operating_system.dart @@ -9,15 +9,21 @@ abstract class OperatingSystem { static const String macos = 'macos'; static const String windows = 'windows'; + /// Flag which determines whether current platform is Android. static bool get isAndroid => defaultTargetPlatform == TargetPlatform.android; + /// Flag which determines whether current platform is iOS. static bool get isIOS => defaultTargetPlatform == TargetPlatform.iOS; + /// Flag which determines whether current platform is MacOS. static bool get isMacOS => defaultTargetPlatform == TargetPlatform.macOS; + /// Flag which determines whether current platform is Windows. static bool get isWindows => defaultTargetPlatform == TargetPlatform.windows; + /// Flag which determines whether current platform is Linux. static bool get isLinux => defaultTargetPlatform == TargetPlatform.linux; + /// Flag which determines whether current platform is Fuchsia. static bool get isFuchsia => defaultTargetPlatform == TargetPlatform.fuchsia; } diff --git a/packages/alice/lib/model/alice_configuration.dart b/packages/alice/lib/model/alice_configuration.dart new file mode 100644 index 00000000..e9e6d6ca --- /dev/null +++ b/packages/alice/lib/model/alice_configuration.dart @@ -0,0 +1,87 @@ +import 'package:alice/core/alice_logger.dart'; +import 'package:alice/core/alice_memory_storage.dart'; +import 'package:alice/core/alice_storage.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; + +class AliceConfiguration with EquatableMixin { + /// Default max calls count used in default memory storage. + static const _defaultMaxCalls = 1000; + + /// Default max logs count. + static const _defaultMaxLogs = 1000; + + /// Should user be notified with notification when there's new request caught + /// by Alice. Default value is true. + final bool showNotification; + + /// Should inspector be opened on device shake (works only with physical + /// with sensors). Default value is true. + final bool showInspectorOnShake; + + /// Icon url for notification. Default value is '@mipmap/ic_launcher'. + final String notificationIcon; + + /// Directionality of app. Directionality of the app will be used if set to + /// null. Default value is null. + final TextDirection? directionality; + + /// Flag used to show/hide share button + final bool showShareButton; + + /// Navigator key used to open inspector. Default value is null. + final GlobalKey? navigatorKey; + + /// Storage where calls will be saved. The default storage is memory storage. + final AliceStorage aliceStorage; + + /// Logger instance. + final AliceLogger aliceLogger; + + AliceConfiguration({ + this.showNotification = true, + this.showInspectorOnShake = true, + this.notificationIcon = '@mipmap/ic_launcher', + this.directionality, + this.showShareButton = true, + GlobalKey? navigatorKey, + AliceStorage? storage, + AliceLogger? logger, + }) : aliceStorage = + storage ?? AliceMemoryStorage(maxCallsCount: _defaultMaxCalls), + navigatorKey = navigatorKey ?? GlobalKey(), + aliceLogger = logger ?? AliceLogger(maximumSize: _defaultMaxLogs); + + AliceConfiguration copyWith({ + GlobalKey? navigatorKey, + bool? showNotification, + bool? showInspectorOnShake, + String? notificationIcon, + TextDirection? directionality, + bool? showShareButton, + AliceStorage? aliceStorage, + AliceLogger? aliceLogger, + }) => + AliceConfiguration( + showNotification: showNotification ?? this.showNotification, + showInspectorOnShake: showInspectorOnShake ?? this.showInspectorOnShake, + notificationIcon: notificationIcon ?? this.notificationIcon, + directionality: directionality ?? this.directionality, + showShareButton: showShareButton ?? this.showShareButton, + navigatorKey: navigatorKey ?? this.navigatorKey, + storage: aliceStorage ?? this.aliceStorage, + logger: aliceLogger ?? this.aliceLogger, + ); + + @override + List get props => [ + showNotification, + showInspectorOnShake, + notificationIcon, + directionality, + showShareButton, + navigatorKey, + aliceStorage, + aliceLogger, + ]; +} diff --git a/packages/alice/lib/model/alice_http_call.dart b/packages/alice/lib/model/alice_http_call.dart index 675cf57f..55ca6cd9 100644 --- a/packages/alice/lib/model/alice_http_call.dart +++ b/packages/alice/lib/model/alice_http_call.dart @@ -5,13 +5,10 @@ import 'package:equatable/equatable.dart'; /// Definition of http calls data holder. class AliceHttpCall with EquatableMixin { - AliceHttpCall(this.id) { - loading = true; - createdTime = DateTime.now(); - } + AliceHttpCall(this.id); final int id; - late DateTime createdTime; + final DateTime createdTime = DateTime.now(); String client = ''; bool loading = true; bool secure = false; @@ -25,11 +22,6 @@ class AliceHttpCall with EquatableMixin { AliceHttpResponse? response; AliceHttpError? error; - void setResponse(AliceHttpResponse response) { - this.response = response; - loading = false; - } - @override List get props => [ id, diff --git a/packages/alice/lib/model/alice_translation.dart b/packages/alice/lib/model/alice_translation.dart index 477ecf7f..4b966d19 100644 --- a/packages/alice/lib/model/alice_translation.dart +++ b/packages/alice/lib/model/alice_translation.dart @@ -78,6 +78,7 @@ enum AliceTranslationKey { callsListStats, callsListSave, logsEmpty, + logsError, logsItemError, logsItemStackTrace, logsCopied, diff --git a/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart b/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart index 1e46cc9f..26c3b8f9 100644 --- a/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart +++ b/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart @@ -74,17 +74,18 @@ class _AliceCallDetailsPageState extends State AliceCallErrorScreen(call: widget.call), ], ), - floatingActionButton: widget.core.showShareButton ?? false - ? FloatingActionButton( - backgroundColor: AliceTheme.lightRed, - key: const Key('share_key'), - onPressed: _shareCall, - child: const Icon( - Icons.share, - color: AliceTheme.white, - ), - ) - : null, + floatingActionButton: + widget.core.configuration.showShareButton + ? FloatingActionButton( + backgroundColor: AliceTheme.lightRed, + key: const Key('share_key'), + onPressed: _shareCall, + child: const Icon( + Icons.share, + color: AliceTheme.white, + ), + ) + : null, ), ); } @@ -102,6 +103,8 @@ class _AliceCallDetailsPageState extends State ); } + /// Called when share button has been pressed. It encodes the [widget.call] + /// and tries to invoke system action to share it. void _shareCall() async { await AliceExportHelper.shareCall(context: context, call: widget.call); } diff --git a/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart b/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart index 17289eae..366d5965 100644 --- a/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart +++ b/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart @@ -72,6 +72,7 @@ class _GeneralDataColumn extends StatelessWidget { } } +/// Widget which renders column with headers of [call]. class _HeaderDataColumn extends StatelessWidget { final AliceHttpCall call; @@ -99,6 +100,7 @@ class _HeaderDataColumn extends StatelessWidget { } } +/// Widget which renders column with body of [call]. class _BodyDataColumn extends StatefulWidget { const _BodyDataColumn({required this.call}); @@ -148,18 +150,21 @@ class _BodyDataColumnState extends State<_BodyDataColumn> { } } + /// Checks whether content type of response is image. bool _isImageResponse() { return _getContentTypeOfResponse()! .toLowerCase() .contains(_imageContentType); } + /// Checks whether content type of response is video bool _isVideoResponse() { return _getContentTypeOfResponse()! .toLowerCase() .contains(_videoContentType); } + /// Checks whether content type of response is text. bool _isTextResponse() { final responseContentTypeLowerCase = _getContentTypeOfResponse()!.toLowerCase(); @@ -169,20 +174,24 @@ class _BodyDataColumnState extends State<_BodyDataColumn> { responseContentTypeLowerCase.contains(_textContentType); } + /// Parses headers and returns content type of response. It may return null. String? _getContentTypeOfResponse() { return AliceParser.getContentType( context: context, headers: call.response?.headers); } + /// Checks whether response body is large (more than [_largeOutputSize]. bool _isLargeResponseBody() => call.response?.body.toString().length.gt(_largeOutputSize) ?? false; + /// Called when show large body has been pressed. void onShowLargeBodyPressed() { setState(() { _showLargeBody = true; }); } + /// Called when show unsupported body has been pressed. void onShowUnsupportedBodyPressed() { setState(() { _showUnsupportedBody = true; @@ -190,6 +199,7 @@ class _BodyDataColumnState extends State<_BodyDataColumn> { } } +/// Widget which renders body as image. class _ImageBody extends StatelessWidget { const _ImageBody({ required this.call, @@ -235,6 +245,7 @@ class _ImageBody extends StatelessWidget { ); } + /// Builds request headers to access the image. Map _buildRequestHeaders() { final requestHeaders = {}; if (call.request?.headers != null) { @@ -251,6 +262,7 @@ class _ImageBody extends StatelessWidget { } } +/// Widget which renders large body as a text. class _LargeTextBody extends StatelessWidget { const _LargeTextBody({ required this.showLargeBody, @@ -293,6 +305,7 @@ class _LargeTextBody extends StatelessWidget { } } +/// Widget which renders body as a text. class _TextBody extends StatelessWidget { const _TextBody({required this.call}); @@ -313,6 +326,7 @@ class _TextBody extends StatelessWidget { } } +/// Widget which renders body as video. class _VideoBody extends StatelessWidget { const _VideoBody({required this.call}); @@ -344,11 +358,15 @@ class _VideoBody extends StatelessWidget { } } +/// Widget which renders unknown body message. class _UnknownBody extends StatelessWidget { - const _UnknownBody( - {required this.call, - required this.showUnsupportedBody, - required this.onShowUnsupportedBodyPressed}); + static const _contentType = "[contentType]"; + + const _UnknownBody({ + required this.call, + required this.showUnsupportedBody, + required this.onShowUnsupportedBodyPressed, + }); final AliceHttpCall call; final bool showUnsupportedBody; @@ -379,7 +397,7 @@ class _UnknownBody extends StatelessWidget { value: context .i18n(AliceTranslationKey.callResponseBodyUnknown) .replaceAll( - "[contentType]", + _contentType, contentType, ), ), diff --git a/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart b/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart index 2b0beb6a..9f5c53f7 100644 --- a/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart +++ b/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart @@ -1,7 +1,6 @@ // ignore_for_file: use_build_context_synchronously import 'package:alice/core/alice_core.dart'; -import 'package:alice/core/alice_logger.dart'; import 'package:alice/helper/operating_system.dart'; import 'package:alice/model/alice_export_result.dart'; import 'package:alice/model/alice_http_call.dart'; @@ -25,11 +24,9 @@ import 'package:open_filex/open_filex.dart'; /// and search calls. class AliceCallsListPage extends StatefulWidget { final AliceCore core; - final AliceLogger? logger; const AliceCallsListPage({ required this.core, - this.logger, super.key, }); @@ -142,7 +139,7 @@ class _AliceCallsListPageState extends State ), AliceLogsScreen( scrollController: _scrollController, - aliceLogger: widget.logger, + aliceLogger: widget.core.configuration.aliceLogger, isAndroidRawLogsEnabled: isAndroidRawLogsEnabled, ), ], @@ -192,9 +189,9 @@ class _AliceCallsListPageState extends State /// Called when logs clear button has been pressed. void _onLogsClearPressed() => setState(() { if (isAndroidRawLogsEnabled) { - widget.logger?.clearAndroidRawLogs(); + widget.core.configuration.aliceLogger.clearAndroidRawLogs(); } else { - widget.logger?.clearLogs(); + widget.core.configuration.aliceLogger.clearLogs(); } }); diff --git a/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart b/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart index 9ed7097e..4bd970a6 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart @@ -19,6 +19,7 @@ class AliceCallsListScreen extends StatelessWidget { final bool sortAscending; final void Function(AliceHttpCall) onListItemClicked; + /// Returns sorted calls based [sortOption] and [sortAscending]. List get _sortedCalls => switch (sortOption) { AliceCallsListSortOption.time => sortAscending ? (calls diff --git a/packages/alice/lib/ui/calls_list/widget/alice_error_logs_widget.dart b/packages/alice/lib/ui/calls_list/widget/alice_error_logs_widget.dart new file mode 100644 index 00000000..4697be59 --- /dev/null +++ b/packages/alice/lib/ui/calls_list/widget/alice_error_logs_widget.dart @@ -0,0 +1,34 @@ +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_theme.dart'; +import 'package:flutter/material.dart'; + +/// Widget which renders empty text for calls list. +class AliceErrorLogsWidget extends StatelessWidget { + const AliceErrorLogsWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 32), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + color: AliceTheme.red, + ), + const SizedBox(height: 6), + Text( + context.i18n(AliceTranslationKey.logsItemError), + style: const TextStyle(fontSize: 18), + ), + ], + ), + ), + ); + } +} diff --git a/packages/alice/lib/ui/calls_list/widget/alice_inspector_screen.dart b/packages/alice/lib/ui/calls_list/widget/alice_inspector_screen.dart index a90b8c6a..662a2a4f 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_inspector_screen.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_inspector_screen.dart @@ -5,6 +5,8 @@ import 'package:alice/ui/calls_list/widget/alice_calls_list_screen.dart'; import 'package:alice/ui/calls_list/widget/alice_empty_logs_widget.dart'; import 'package:flutter/material.dart'; +/// Screen which is hosted in calls list page. It displays HTTP calls. It allows +/// to search call and sort items based on provided criteria. class AliceInspectorScreen extends StatefulWidget { const AliceInspectorScreen({ super.key, diff --git a/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart b/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart index 2c83abce..a6cf9b90 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:alice/model/alice_log.dart'; import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/calls_list/widget/alice_empty_logs_widget.dart'; +import 'package:alice/ui/calls_list/widget/alice_error_logs_widget.dart'; import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_scroll_behavior.dart'; import 'package:alice/ui/common/alice_theme.dart'; @@ -12,15 +14,13 @@ import 'package:flutter/services.dart'; /// Widget which renders log list for calls list page. class AliceLogListWidget extends StatefulWidget { const AliceLogListWidget({ - required this.logsListenable, + required this.logsStream, required this.scrollController, - required this.emptyWidget, super.key, }); - final ValueListenable> logsListenable; + final Stream>? logsStream; final ScrollController? scrollController; - final Widget emptyWidget; @override State createState() => _AliceLogListWidgetState(); @@ -32,11 +32,23 @@ class _AliceLogListWidgetState extends State { @override Widget build(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: widget.logsListenable, - builder: (_, List logs, __) { + return StreamBuilder>( + stream: widget.logsStream, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.none || + snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.hasError) { + return const AliceErrorLogsWidget(); + } + + final logs = snapshot.data ?? []; if (logs.isEmpty) { - return widget.emptyWidget; + return const AliceEmptyLogsWidget(); } final List filteredLogs = [ diff --git a/packages/alice/lib/ui/calls_list/widget/alice_logs_screen.dart b/packages/alice/lib/ui/calls_list/widget/alice_logs_screen.dart index ed49e728..e56169a7 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_logs_screen.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_logs_screen.dart @@ -23,12 +23,10 @@ class AliceLogsScreen extends StatelessWidget { ? AliceRawLogListWidget( scrollController: scrollController, getRawLogs: aliceLogger?.getAndroidRawLogs(), - emptyWidget: const AliceEmptyLogsWidget(), ) : AliceLogListWidget( - logsListenable: aliceLogger!.listenable, + logsStream: aliceLogger?.logsStream, scrollController: scrollController, - emptyWidget: const AliceEmptyLogsWidget(), ) : const AliceEmptyLogsWidget(); } diff --git a/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart b/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart index 58537d7f..81caeb1d 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart @@ -1,4 +1,6 @@ import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/calls_list/widget/alice_empty_logs_widget.dart'; +import 'package:alice/ui/calls_list/widget/alice_error_logs_widget.dart'; import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,48 +10,62 @@ class AliceRawLogListWidget extends StatelessWidget { const AliceRawLogListWidget({ required this.scrollController, required this.getRawLogs, - required this.emptyWidget, super.key, }); final ScrollController scrollController; final Future? getRawLogs; - final Widget emptyWidget; @override Widget build(BuildContext context) { return FutureBuilder( future: getRawLogs, builder: (context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - if (snapshot.data?.isNotEmpty == true) { - return Scrollbar( - thickness: 8, - controller: scrollController, - child: SingleChildScrollView( - controller: scrollController, - child: Padding( - padding: const EdgeInsets.all(8), - child: InkWell( - onLongPress: () => - _copyToClipboard(snapshot.data ?? '', context), - child: Text( - snapshot.data ?? '', - style: const TextStyle(fontSize: 10), - ), - ), + if (snapshot.connectionState == ConnectionState.none || + snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.hasError) { + return const AliceErrorLogsWidget(); + } + + final logs = snapshot.data ?? ''; + if (logs.isEmpty) { + return const AliceEmptyLogsWidget(); + } + + return Scrollbar( + thickness: 8, + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + child: Padding( + padding: const EdgeInsets.all(8), + child: InkWell( + onLongPress: () => _copyToClipboard( + context: context, + text: snapshot.data ?? '', + ), + child: Text( + snapshot.data ?? '', + style: const TextStyle(fontSize: 10), ), ), - ); - } - return emptyWidget; - } - return const Center(child: CircularProgressIndicator()); + ), + ), + ); }, ); } - Future _copyToClipboard(String text, BuildContext context) async { + /// Copies provided text to clipboard and displays info about it. + Future _copyToClipboard({ + required BuildContext context, + required String text, + }) async { await Clipboard.setData(ClipboardData(text: text)); if (context.mounted) { diff --git a/packages/alice/lib/ui/common/alice_context_ext.dart b/packages/alice/lib/ui/common/alice_context_ext.dart index 30022cd6..7aa20c30 100644 --- a/packages/alice/lib/ui/common/alice_context_ext.dart +++ b/packages/alice/lib/ui/common/alice_context_ext.dart @@ -2,7 +2,10 @@ import 'package:alice/model/alice_translation.dart'; import 'package:alice/core/alice_translations.dart'; import 'package:flutter/material.dart'; +/// Extension for [BuildContext]. extension AliceContextExt on BuildContext { + /// Tries to translate given key based on current language code collected from + /// locale. If it fails to translate [key], it will return [key] itself. String i18n(AliceTranslationKey key) { try { final locale = Localizations.localeOf(this); diff --git a/packages/alice/lib/ui/common/alice_navigation.dart b/packages/alice/lib/ui/common/alice_navigation.dart index 6c2f5152..39c85a51 100644 --- a/packages/alice/lib/ui/common/alice_navigation.dart +++ b/packages/alice/lib/ui/common/alice_navigation.dart @@ -1,5 +1,4 @@ import 'package:alice/core/alice_core.dart'; -import 'package:alice/core/alice_logger.dart'; import 'package:alice/model/alice_http_call.dart'; import 'package:alice/ui/call_details/page/alice_call_details_page.dart'; import 'package:alice/ui/calls_list/page/alice_calls_list_page.dart'; @@ -11,11 +10,10 @@ class AliceNavigation { /// Navigates to calls list page. static Future navigateToCallsList({ required AliceCore core, - required AliceLogger logger, }) { return _navigateToPage( core: core, - child: AliceCallsListPage(core: core, logger: logger), + child: AliceCallsListPage(core: core), ); } diff --git a/packages/alice/lib/ui/common/alice_page.dart b/packages/alice/lib/ui/common/alice_page.dart index 748815f6..251ffb30 100644 --- a/packages/alice/lib/ui/common/alice_page.dart +++ b/packages/alice/lib/ui/common/alice_page.dart @@ -12,7 +12,8 @@ class AlicePage extends StatelessWidget { @override Widget build(BuildContext context) { return Directionality( - textDirection: core.directionality ?? Directionality.of(context), + textDirection: + core.configuration.directionality ?? Directionality.of(context), child: Theme( data: AliceTheme.getTheme(), child: child, diff --git a/packages/alice/lib/utils/alice_parser.dart b/packages/alice/lib/utils/alice_parser.dart index df5874b7..e4ebdcbe 100644 --- a/packages/alice/lib/utils/alice_parser.dart +++ b/packages/alice/lib/utils/alice_parser.dart @@ -94,6 +94,8 @@ class AliceParser { return context.i18n(AliceTranslationKey.unknown); } + /// Parses headers from [dynamic] to [Map], if possible. + /// Otherwise it will throw error. static Map parseHeaders({dynamic headers}) { if (headers is Map) { return headers; diff --git a/packages/alice/lib/utils/curl.dart b/packages/alice/lib/utils/curl.dart index ad4e980d..48ffcaef 100644 --- a/packages/alice/lib/utils/curl.dart +++ b/packages/alice/lib/utils/curl.dart @@ -2,59 +2,62 @@ import 'dart:io' show HttpHeaders; import 'package:alice/model/alice_http_call.dart'; -String getCurlCommand(AliceHttpCall call) { - bool compressed = false; - final StringBuffer curlCmd = StringBuffer('curl'); - - curlCmd.write(' -X ${call.method}'); +class Curl { + /// Builds Curl command based on [call] instance. + static String getCurlCommand(AliceHttpCall call) { + bool compressed = false; + final StringBuffer curlCmd = StringBuffer('curl'); + + curlCmd.write(' -X ${call.method}'); + + for (final MapEntry header + in call.request?.headers.entries ?? []) { + if (header.key.toLowerCase() == HttpHeaders.acceptEncodingHeader && + header.value.toString().toLowerCase() == 'gzip') { + compressed = true; + } - for (final MapEntry header - in call.request?.headers.entries ?? []) { - if (header.key.toLowerCase() == HttpHeaders.acceptEncodingHeader && - header.value.toString().toLowerCase() == 'gzip') { - compressed = true; + curlCmd.write(' -H "${header.key}: ${header.value}"'); } - curlCmd.write(' -H "${header.key}: ${header.value}"'); - } - - final String? requestBody = call.request?.body.toString(); - if (requestBody?.isNotEmpty ?? false) { - // try to keep to a single line and use a subshell to preserve any line - // breaks - curlCmd.write(" --data \$'${requestBody?.replaceAll("\n", r"\n")}'"); - } + final String? requestBody = call.request?.body.toString(); + if (requestBody?.isNotEmpty ?? false) { + // try to keep to a single line and use a subshell to preserve any line + // breaks + curlCmd.write(" --data \$'${requestBody?.replaceAll("\n", r"\n")}'"); + } - final Map? queryParamMap = call.request?.queryParameters; - int paramCount = queryParamMap?.keys.length ?? 0; - final StringBuffer queryParams = StringBuffer(); - - if (paramCount > 0) { - queryParams.write('?'); - for (final MapEntry queryParam - in queryParamMap?.entries ?? []) { - queryParams.write('${queryParam.key}=${queryParam.value}'); - paramCount--; - if (paramCount > 0) { - queryParams.write('&'); + final Map? queryParamMap = call.request?.queryParameters; + int paramCount = queryParamMap?.keys.length ?? 0; + final StringBuffer queryParams = StringBuffer(); + + if (paramCount > 0) { + queryParams.write('?'); + for (final MapEntry queryParam + in queryParamMap?.entries ?? []) { + queryParams.write('${queryParam.key}=${queryParam.value}'); + paramCount--; + if (paramCount > 0) { + queryParams.write('&'); + } } } - } - // If server already has http(s) don't add it again - if (call.server.contains('http://') || call.server.contains('https://')) { - // ignore: join_return_with_assignment - curlCmd.write( - "${compressed ? " --compressed " : " "}" - "${"'${call.server}${call.endpoint}$queryParams'"}", - ); - } else { - // ignore: join_return_with_assignment - curlCmd.write( - "${compressed ? " --compressed " : " "}" - "${"'${call.secure ? 'https://' : 'http://'}${call.server}${call.endpoint}$queryParams'"}", - ); - } + // If server already has http(s) don't add it again + if (call.server.contains('http://') || call.server.contains('https://')) { + // ignore: join_return_with_assignment + curlCmd.write( + "${compressed ? " --compressed " : " "}" + "${"'${call.server}${call.endpoint}$queryParams'"}", + ); + } else { + // ignore: join_return_with_assignment + curlCmd.write( + "${compressed ? " --compressed " : " "}" + "${"'${call.secure ? 'https://' : 'http://'}${call.server}${call.endpoint}$queryParams'"}", + ); + } - return curlCmd.toString(); + return curlCmd.toString(); + } } diff --git a/packages/alice/lib/utils/shake_detector.dart b/packages/alice/lib/utils/shake_detector.dart index 5ab13387..4097d4ab 100644 --- a/packages/alice/lib/utils/shake_detector.dart +++ b/packages/alice/lib/utils/shake_detector.dart @@ -1,7 +1,3 @@ -///Code from https://github.com/deven98/shake -///Seems to be not maintained for almost 2 years... (01.03.2021). -library; - import 'dart:async'; import 'dart:math'; @@ -85,10 +81,12 @@ class ShakeDetector { /// Stops listening to accelerometer events void stopListening() { - streamSubscription?.cancel(); + dispose(); } + /// Disposes all subscriptions. void dispose() { streamSubscription?.cancel(); + streamSubscription = null; } } diff --git a/packages/alice/pubspec.yaml b/packages/alice/pubspec.yaml index 22cf999a..ef0642ee 100644 --- a/packages/alice/pubspec.yaml +++ b/packages/alice/pubspec.yaml @@ -1,6 +1,6 @@ name: alice description: Alice is an HTTP Inspector tool which helps debugging http requests. It catches and stores http requests and responses, which can be viewed via simple UI. -version: 1.0.0-dev.9 +version: 1.0.0-dev.10 homepage: https://github.com/jhomlala/alice repository: https://github.com/jhomlala/alice topics: diff --git a/packages/alice/test/alice_test.dart b/packages/alice/test/alice_test.dart new file mode 100644 index 00000000..abce8383 --- /dev/null +++ b/packages/alice/test/alice_test.dart @@ -0,0 +1,84 @@ +import 'package:alice/alice.dart'; +import 'package:alice/core/alice_adapter.dart'; +import 'package:alice/core/alice_logger.dart'; +import 'package:alice/core/alice_storage.dart'; +import 'package:alice/model/alice_configuration.dart'; +import 'package:alice/model/alice_http_call.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'mock/mocked_data.dart'; + +void main() { + group("Alice", () { + late Alice alice; + late AliceStorage aliceStorage; + late AliceLogger aliceLogger; + setUp(() { + aliceStorage = AliceMemoryStorage(maxCallsCount: 1000); + aliceLogger = AliceLogger(maximumSize: 1000); + alice = Alice( + configuration: AliceConfiguration( + showInspectorOnShake: false, + showNotification: false, + logger: aliceLogger, + storage: aliceStorage, + ), + ); + }); + + test("should set new navigator key", () { + final navigatorKey = GlobalKey(); + + expect(alice.getNavigatorKey() != navigatorKey, true); + + alice.setNavigatorKey(navigatorKey); + + expect(alice.getNavigatorKey() == navigatorKey, true); + }); + + test("should add log", () { + final log = AliceLog(message: "test"); + + alice.addLog(log); + + expect(aliceLogger.logs, [log]); + }); + + test("should add logs", () { + final logs = [AliceLog(message: "test 1"), AliceLog(message: "test 2")]; + + alice.addLogs(logs); + + expect(aliceLogger.logs, logs); + }); + + test("should add call", () { + final call = MockedData.getFilledHttpCall(); + + alice.addHttpCall(call); + + expect(aliceStorage.getCalls(), [call]); + }); + + test("should add adapter", () { + final call = MockedData.getFilledHttpCall(); + final adapter = FakeAdapter(); + alice.addAdapter(adapter); + + adapter.addCallLog(call); + + expect(aliceStorage.getCalls(), [call]); + }); + + test("should return is inspector opened flag", () { + expect(alice.isInspectorOpened, false); + }); + }); +} + +class FakeAdapter with AliceAdapter { + void addCallLog(AliceHttpCall call) { + aliceCore.addCall(call); + } +} diff --git a/packages/alice/test/core/alice_core_test.dart b/packages/alice/test/core/alice_core_test.dart index f1d503ec..2a69251b 100644 --- a/packages/alice/test/core/alice_core_test.dart +++ b/packages/alice/test/core/alice_core_test.dart @@ -2,15 +2,15 @@ import 'package:alice/alice.dart'; import 'package:alice/core/alice_core.dart'; import 'package:alice/core/alice_logger.dart'; import 'package:alice/core/alice_storage.dart'; +import 'package:alice/model/alice_configuration.dart'; import 'package:alice/model/alice_http_error.dart'; import 'package:alice/model/alice_http_response.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; import '../mock/alice_logger_mock.dart'; import '../mock/alice_storage_mock.dart'; -import '../mocked_data.dart'; +import '../mock/mocked_data.dart'; void main() { late AliceCore aliceCore; @@ -18,6 +18,7 @@ void main() { late AliceLogger aliceLogger; setUp(() { + TestWidgetsFlutterBinding.ensureInitialized(); registerFallbackValue(MockedData.getLoadingHttpCall()); registerFallbackValue(AliceHttpError()); registerFallbackValue(AliceHttpResponse()); @@ -28,12 +29,12 @@ void main() { when(() => aliceStorage.callsStream) .thenAnswer((_) => const Stream.empty()); aliceCore = AliceCore( - GlobalKey(), - showNotification: false, - showInspectorOnShake: false, - notificationIcon: "", - aliceStorage: aliceStorage, - aliceLogger: aliceLogger, + configuration: AliceConfiguration( + showNotification: false, + showInspectorOnShake: false, + storage: aliceStorage, + logger: aliceLogger, + ), ); }); diff --git a/packages/alice/test/core/alice_logger_test.dart b/packages/alice/test/core/alice_logger_test.dart index a46e0ce5..c41be6a7 100644 --- a/packages/alice/test/core/alice_logger_test.dart +++ b/packages/alice/test/core/alice_logger_test.dart @@ -40,20 +40,5 @@ void main() { expect(aliceLogger.logs.isEmpty, true); }); - - test("should set maximum size", () { - final logs = [ - AliceLog(message: "test"), - AliceLog(message: "test2"), - ]; - - aliceLogger.addAll(logs); - - expect(aliceLogger.logs.length, 2); - - aliceLogger.maximumSize = 1; - - expect(aliceLogger.logs.length, 1); - }); }); } diff --git a/packages/alice/test/core/alice_memory_storage_test.dart b/packages/alice/test/core/alice_memory_storage_test.dart index 32ba44df..e2666106 100644 --- a/packages/alice/test/core/alice_memory_storage_test.dart +++ b/packages/alice/test/core/alice_memory_storage_test.dart @@ -6,7 +6,7 @@ import 'package:alice/model/alice_http_response.dart'; import 'package:test/expect.dart'; import 'package:test/scaffolding.dart'; -import '../mocked_data.dart'; +import '../mock/mocked_data.dart'; void main() { late AliceMemoryStorage storage; @@ -39,12 +39,14 @@ void main() { storage .addCall(MockedData.getHttpCallWithResponseStatus(statusCode: 500)); storage.addCall(MockedData.getLoadingHttpCall()); + storage.addCall(MockedData.getHttpCallWithResponseStatus(statusCode: -1)); + storage.addCall(MockedData.getHttpCallWithResponseStatus(statusCode: 0)); expect(storage.getStats(), ( - total: 8, + total: 10, successes: 2, redirects: 2, - errors: 3, + errors: 5, loading: 1, )); }); diff --git a/packages/alice/test/helper/alice_export_helper_test.dart b/packages/alice/test/helper/alice_export_helper_test.dart index 94452a1e..0dd346e0 100644 --- a/packages/alice/test/helper/alice_export_helper_test.dart +++ b/packages/alice/test/helper/alice_export_helper_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../mock/build_context_mock.dart'; -import '../mocked_data.dart'; +import '../mock/mocked_data.dart'; void main() { late BuildContext context; diff --git a/packages/alice/test/mocked_data.dart b/packages/alice/test/mock/mocked_data.dart similarity index 100% rename from packages/alice/test/mocked_data.dart rename to packages/alice/test/mock/mocked_data.dart diff --git a/packages/alice_dio/pubspec.yaml b/packages/alice_dio/pubspec.yaml index 24c5d5be..4f6e0d09 100644 --- a/packages/alice_dio/pubspec.yaml +++ b/packages/alice_dio/pubspec.yaml @@ -1,6 +1,6 @@ name: alice_dio description: "Alice + Dio integration. It contains plugin for Alice which allows to use Dio package." -version: 1.0.3 +version: 1.0.4 repository: https://github.com/jhomlala/alice homepage: https://github.com/jhomlala/alice topics: diff --git a/packages/alice_objectbox/CHANGELOG.md b/packages/alice_objectbox/CHANGELOG.md index f6767c03..fb85da39 100644 --- a/packages/alice_objectbox/CHANGELOG.md +++ b/packages/alice_objectbox/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.2 +* Fixed issue with invalid count of calls in notification. + # 1.0.1 * Updated documentation. diff --git a/packages/alice_objectbox/lib/alice_objectbox.dart b/packages/alice_objectbox/lib/alice_objectbox.dart index 23128bc4..c868d621 100644 --- a/packages/alice_objectbox/lib/alice_objectbox.dart +++ b/packages/alice_objectbox/lib/alice_objectbox.dart @@ -119,11 +119,12 @@ class AliceObjectBox implements AliceStorage { .count(), errors: (_store.httpCalls.query() ..link( - CachedAliceHttpCall_.responseRel, - CachedAliceHttpResponse_.status - .greaterOrEqual(400) - .and(CachedAliceHttpResponse_.status.lessThan(600)), - )) + CachedAliceHttpCall_.responseRel, + CachedAliceHttpResponse_.status + .greaterOrEqual(400) + .and(CachedAliceHttpResponse_.status.lessThan(600)) + .and(CachedAliceHttpResponse_.status.equals(-1)) + .and(CachedAliceHttpResponse_.status.equals(0)))) .build() .count(), loading: _store.httpCalls diff --git a/packages/alice_objectbox/lib/alice_objectbox_store.dart b/packages/alice_objectbox/lib/alice_objectbox_store.dart index 5f9ff667..623baff3 100644 --- a/packages/alice_objectbox/lib/alice_objectbox_store.dart +++ b/packages/alice_objectbox/lib/alice_objectbox_store.dart @@ -1,13 +1,12 @@ import 'dart:io' show Directory; -import 'package:alice/core/alice_store.dart'; import 'package:alice_objectbox/model/cached_alice_http_call.dart'; import 'package:alice_objectbox/objectbox.g.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; -/// Implementation of [AliceStore] using ObjectBox. -class AliceObjectBoxStore implements AliceStore { +/// Implementation of store for ObjectBox. +class AliceObjectBoxStore { AliceObjectBoxStore._create( this._store, { bool persistent = true, @@ -53,7 +52,6 @@ class AliceObjectBoxStore implements AliceStore { }; /// This will remove all the items from all the boxes - @override void clear() { for (final Box box in _boxes.values) { box.removeAll(); diff --git a/packages/alice_objectbox/lib/model/cached_alice_http_call.dart b/packages/alice_objectbox/lib/model/cached_alice_http_call.dart index 7616d7f4..baa8358a 100644 --- a/packages/alice_objectbox/lib/model/cached_alice_http_call.dart +++ b/packages/alice_objectbox/lib/model/cached_alice_http_call.dart @@ -107,12 +107,6 @@ class CachedAliceHttpCall implements AliceHttpCall { @internal final ToOne errorRel = ToOne(); - @override - void setResponse(AliceHttpResponse response) { - this.response = response; - loading = false; - } - @override List get props => [ id, diff --git a/packages/alice_objectbox/pubspec.yaml b/packages/alice_objectbox/pubspec.yaml index 3f5e962b..b5ce5c09 100644 --- a/packages/alice_objectbox/pubspec.yaml +++ b/packages/alice_objectbox/pubspec.yaml @@ -1,6 +1,6 @@ name: alice_objectbox description: "Alice + ObjectBox integration. It contains a plugin for Alice which stores http requests and responses in an ObjectBox NoSQL database." -version: 1.0.1 +version: 1.0.2 repository: https://github.com/jhomlala/alice homepage: https://github.com/jhomlala/alice topics: