From ada521e2fd31bfda1cae53e0d71358e814108098 Mon Sep 17 00:00:00 2001
From: ice-endymion <188437551+ice-endymion@users.noreply.github.com>
Date: Thu, 9 Jan 2025 14:20:57 +0100
Subject: [PATCH] feat: logger (#524)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Description
Change of logging approach
## Additional Notes
- Refactor logging to have one logger
- Use logger interface from
[NostrDart](https://github.com/ice-blockchain/nostr-dart/blob/master/lib/src/nostr_dart_logger.dart)
to control logs from app
- Use `TalkerDioLogger` as Interceptor for `ionIdentityClient` to
control logs from app
- Integrate [Talker](https://pub.dev/packages/talker) package for logs
- Add Debug menu, shake 👋 your phone or use hotkeys in simulators(🤖
cmd+m, 🍏 ctrl+cmd+z)
- Refactor feature flags (logs can be on/off by feature flags)
- Now you can export logs and share them
## Type of Change
- [ ] Bug fix
- [x] New feature
- [x] Breaking change
- [x] Refactoring
- [ ] Documentation
- [ ] Chore
## Screenshots
---
.../main/kotlin/io/ion/app/MainActivity.kt | 14 +-
ios/Podfile.lock | 12 +-
.../discover_creators/discover_creators.dart | 2 +-
.../providers/force_update_provider.c.dart | 2 -
.../features/core/model/feature_flags.dart | 9 ++
.../core/providers/dio_provider.c.dart | 11 +-
.../core/providers/env_provider.c.dart | 37 ++---
.../providers/feature_flags_provider.c.dart | 63 ++-------
.../core/providers/init_provider.c.dart | 20 +--
.../core/views/pages/error_modal.dart | 5 +-
lib/app/features/debug/views/debug_page.dart | 75 ++++++++++
.../debug/views/debug_shake_gesture.dart | 49 +++++++
.../feed/views/pages/feed_page/feed_page.dart | 2 +-
.../router/components/app_router_builder.dart | 5 +-
.../providers/go_router_provider.c.dart | 5 +-
.../utils/show_simple_bottom_sheet.dart | 2 +
.../ion_identity/ion_identity_provider.c.dart | 16 ++-
lib/app/services/logger/config.dart | 15 --
lib/app/services/logger/logger.dart | 45 +++++-
lib/app/services/nostr/nostr.dart | 7 +-
lib/app/services/nostr/nostr_logger.dart | 26 ++++
.../services/riverpod/riverpod_logger.dart | 57 --------
.../riverpod/root_provider_scope.dart | 25 ----
lib/main.dart | 13 +-
.../ion_identity_client/example/pubspec.lock | 8 --
.../network_service_locator.dart | 13 +-
.../lib/src/ion_identity_config.dart | 11 +-
packages/ion_identity_client/pubspec.yaml | 1 -
pubspec.lock | 130 +++++++++++++-----
pubspec.yaml | 7 +-
test/user/follow_list_test.dart | 2 +-
test/user/interests_set_test.dart | 2 +-
test/user/interests_test.dart | 2 +-
test/user/user_delegation_test.dart | 2 +-
34 files changed, 421 insertions(+), 274 deletions(-)
create mode 100644 lib/app/features/debug/views/debug_page.dart
create mode 100644 lib/app/features/debug/views/debug_shake_gesture.dart
delete mode 100644 lib/app/services/logger/config.dart
create mode 100644 lib/app/services/nostr/nostr_logger.dart
delete mode 100644 lib/app/services/riverpod/riverpod_logger.dart
delete mode 100644 lib/app/services/riverpod/root_provider_scope.dart
diff --git a/android/app/src/main/kotlin/io/ion/app/MainActivity.kt b/android/app/src/main/kotlin/io/ion/app/MainActivity.kt
index 03717d17c..6d1f8fe9f 100644
--- a/android/app/src/main/kotlin/io/ion/app/MainActivity.kt
+++ b/android/app/src/main/kotlin/io/ion/app/MainActivity.kt
@@ -3,15 +3,27 @@ package io.ion.app
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.banuba.sdk.pe.PhotoCreationActivity
+import android.view.KeyEvent
import com.banuba.sdk.pe.BanubaPhotoEditor
+import com.banuba.sdk.pe.PhotoCreationActivity
import com.banuba.sdk.pe.data.PhotoEditorConfig
+import dev.fluttercommunity.shake_gesture_android.ShakeGesturePlugin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import java.io.File
class MainActivity : FlutterActivity() {
+ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ this.flutterEngine?.plugins?.get(ShakeGesturePlugin::class.java).let { plugin ->
+ if (plugin is ShakeGesturePlugin)
+ plugin.onShake()
+ }
+ }
+
+ return super.onKeyDown(keyCode, event)
+ }
companion object {
// For Photo Editor
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0fe719dcf..4d85be630 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -161,6 +161,8 @@ PODS:
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)
+ - shake_gesture_ios (0.0.1):
+ - Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@@ -241,6 +243,7 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
- quill_native_bridge (from `.symlinks/plugins/quill_native_bridge/ios`)
+ - shake_gesture_ios (from `.symlinks/plugins/shake_gesture_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
@@ -333,6 +336,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/qr_code_scanner/ios"
quill_native_bridge:
:path: ".symlinks/plugins/quill_native_bridge/ios"
+ shake_gesture_ios:
+ :path: ".symlinks/plugins/shake_gesture_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@@ -400,9 +405,10 @@ SPEC CHECKSUMS:
qr_code_scanner: d77f94ecc9abf96d9b9b8fc04ef13f611e5a147a
quill_native_bridge: fd2819cf6da02fb6cbf9de37835f96e798e145eb
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
- share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
- shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
- sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
+ shake_gesture_ios: 64f1f579f314c58445761992a123111b3d7b3492
+ share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
+ shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
+ sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71
sqlite3_flutter_libs: f0b59f6bb2a18597d0796558725007e5a7428397
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
diff --git a/lib/app/features/auth/views/pages/discover_creators/discover_creators.dart b/lib/app/features/auth/views/pages/discover_creators/discover_creators.dart
index 4ca89a1f3..5473e84f5 100644
--- a/lib/app/features/auth/views/pages/discover_creators/discover_creators.dart
+++ b/lib/app/features/auth/views/pages/discover_creators/discover_creators.dart
@@ -37,7 +37,7 @@ class DiscoverCreators extends HookConsumerWidget {
final contentCreators = entitiesPagedData?.data.items?.whereType();
final hideCreatorsWithoutPicture = ref
- .watch(featureFlagsProvider.notifier)
+ .read(featureFlagsProvider.notifier)
.get(HideCreatorsWithoutPicture.hideCreatorsWithoutPicture);
final filteredCreators = hideCreatorsWithoutPicture
diff --git a/lib/app/features/config/providers/force_update_provider.c.dart b/lib/app/features/config/providers/force_update_provider.c.dart
index ee476bcbf..541b4a71d 100644
--- a/lib/app/features/config/providers/force_update_provider.c.dart
+++ b/lib/app/features/config/providers/force_update_provider.c.dart
@@ -42,8 +42,6 @@ class ForceUpdate extends _$ForceUpdate {
}
Future _checkAndUpdateConfig() async {
- await ref.read(envProvider.future);
-
final refetchIntervalInMilliseconds =
ref.read(envProvider.notifier).get(EnvVariable.VERSIONS_CONFIG_REFETCH_INTERVAL);
diff --git a/lib/app/features/core/model/feature_flags.dart b/lib/app/features/core/model/feature_flags.dart
index a0cef6dff..1aedfe66d 100644
--- a/lib/app/features/core/model/feature_flags.dart
+++ b/lib/app/features/core/model/feature_flags.dart
@@ -19,6 +19,15 @@ final class FeedFeatureFlag extends FeatureFlag {
static const showMentionsSuggestions = FeedFeatureFlag._(key: 'showMentionsSuggestions');
}
+final class LoggerFeatureFlag extends FeatureFlag {
+ const LoggerFeatureFlag._({required super.key});
+
+ static const logApp = FeedFeatureFlag._(key: 'logApp');
+ static const logRouters = FeedFeatureFlag._(key: 'logRouters');
+ static const logNostrDart = FeedFeatureFlag._(key: 'logNostrDart');
+ static const logIonIdentityClient = FeedFeatureFlag._(key: 'logIonIdentityClient');
+}
+
///
/// TODO: remove this once before production release
/// It hides creators without picture from the discover creators page
diff --git a/lib/app/features/core/providers/dio_provider.c.dart b/lib/app/features/core/providers/dio_provider.c.dart
index 215bdb187..583bda8db 100644
--- a/lib/app/features/core/providers/dio_provider.c.dart
+++ b/lib/app/features/core/providers/dio_provider.c.dart
@@ -2,8 +2,7 @@
import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:ion/app/services/logger/config.dart';
-import 'package:pretty_dio_logger/pretty_dio_logger.dart';
+import 'package:ion/app/services/logger/logger.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'dio_provider.c.g.dart';
@@ -11,8 +10,12 @@ part 'dio_provider.c.g.dart';
@riverpod
Dio dio(Ref ref) {
final dio = Dio();
- if (LoggerConfig.dioLogsEnabled) {
- dio.interceptors.add(PrettyDioLogger());
+
+ final logger = Logger.talkerDioLogger;
+
+ if (logger != null) {
+ dio.interceptors.add(logger);
}
+
return dio;
}
diff --git a/lib/app/features/core/providers/env_provider.c.dart b/lib/app/features/core/providers/env_provider.c.dart
index f29249bbf..e086d15f8 100644
--- a/lib/app/features/core/providers/env_provider.c.dart
+++ b/lib/app/features/core/providers/env_provider.c.dart
@@ -3,7 +3,6 @@
// ignore_for_file: constant_identifier_names
import 'package:flutter_dotenv/flutter_dotenv.dart';
-import 'package:ion/generated/assets.gen.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'env_provider.c.g.dart';
@@ -21,35 +20,19 @@ enum EnvVariable {
@Riverpod(keepAlive: true)
class Env extends _$Env {
@override
- Future build() async {
- await dotenv.load(fileName: Assets.aApp);
- final notDefined = _getNotDefined();
- if (notDefined.isNotEmpty) {
- throw Exception('Invalid ENV value for $notDefined');
- }
- }
-
- List _getNotDefined() {
- return EnvVariable.values
- .where((EnvVariable element) => dotenv.maybeGet(element.name) == null)
- .toList();
- }
+ void build() {}
+ /// Gets a typed environment variable value.
+ /// Throws if the variable is not found or cannot be converted to type [T].
T get(EnvVariable variable) {
final value = dotenv.get(variable.name);
- if (T == bool) {
- return (value.toLowerCase() == 'true') as T;
- }
-
- if (T == int) {
- return int.parse(value) as T;
- }
-
- if (T == double) {
- return double.parse(value) as T;
- }
-
- return value as T;
+ return switch (T) {
+ bool => (value.toLowerCase() == 'true') as T,
+ int => int.parse(value) as T,
+ double => double.parse(value) as T,
+ String => value as T,
+ _ => throw Exception('Unsupported type $T'),
+ };
}
}
diff --git a/lib/app/features/core/providers/feature_flags_provider.c.dart b/lib/app/features/core/providers/feature_flags_provider.c.dart
index 6a61d9cd7..56ae4a835 100644
--- a/lib/app/features/core/providers/feature_flags_provider.c.dart
+++ b/lib/app/features/core/providers/feature_flags_provider.c.dart
@@ -1,64 +1,31 @@
// SPDX-License-Identifier: ice License 1.0
import 'package:ion/app/features/core/model/feature_flags.dart';
-import 'package:ion/app/services/logger/logger.dart';
+import 'package:ion/app/features/core/providers/env_provider.c.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'feature_flags_provider.c.g.dart';
-abstract class FeatureFlagsService {
- const FeatureFlagsService();
-
- bool? get(FeatureFlag flag);
-
- Set get supportedFlags;
-}
-
-final class LocalFeatureFlagsService extends FeatureFlagsService {
- factory LocalFeatureFlagsService() {
- return const LocalFeatureFlagsService._({
+@Riverpod(keepAlive: true)
+class FeatureFlags extends _$FeatureFlags {
+ @override
+ Map build() {
+ return {
+ /// Local flags
WalletFeatureFlag.buyNftEnabled: false,
FeedFeatureFlag.showTrendingVideo: false,
FeedFeatureFlag.showMentionsSuggestions: false,
HideCreatorsWithoutPicture.hideCreatorsWithoutPicture: true,
- });
- }
-
- const LocalFeatureFlagsService._(this._featuresMap);
-
- final Map _featuresMap;
-
- @override
- bool? get(FeatureFlag flag) => _featuresMap[flag];
- @override
- Set get supportedFlags => _featuresMap.keys.toSet();
-}
-
-@Riverpod(keepAlive: true)
-class FeatureFlags extends _$FeatureFlags {
- late final Set _services;
-
- @override
- Future build() async {
- _services = {
- LocalFeatureFlagsService(),
+ /// Log flags
+ if (ref.watch(envProvider.notifier).get(EnvVariable.SHOW_DEBUG_INFO)) ...{
+ LoggerFeatureFlag.logApp: true,
+ LoggerFeatureFlag.logRouters: false,
+ LoggerFeatureFlag.logNostrDart: true,
+ LoggerFeatureFlag.logIonIdentityClient: true,
+ },
};
}
- bool get(FeatureFlag flag, {bool defaultValue = false}) {
- for (final service in _services) {
- if (service.supportedFlags.contains(flag)) {
- final value = service.get(flag);
-
- if (value != null) {
- return value;
- }
- }
- }
-
- Logger.log('${flag.key} not found.');
-
- return defaultValue;
- }
+ bool get(FeatureFlag flag) => state[flag] ?? false;
}
diff --git a/lib/app/features/core/providers/init_provider.c.dart b/lib/app/features/core/providers/init_provider.c.dart
index 3b3eaa256..87d1f24c3 100644
--- a/lib/app/features/core/providers/init_provider.c.dart
+++ b/lib/app/features/core/providers/init_provider.c.dart
@@ -3,12 +3,15 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/features/auth/providers/auth_provider.c.dart';
import 'package:ion/app/features/auth/providers/onboarding_complete_provider.c.dart';
+import 'package:ion/app/features/core/model/feature_flags.dart';
import 'package:ion/app/features/core/permissions/providers/permissions_provider.c.dart';
-import 'package:ion/app/features/core/providers/env_provider.c.dart';
+import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
import 'package:ion/app/features/core/providers/template_provider.c.dart';
import 'package:ion/app/features/core/providers/window_manager_provider.c.dart';
import 'package:ion/app/features/wallet/data/coins/domain/coin_initializer.c.dart';
+import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/app/services/nostr/nostr.dart';
+import 'package:ion/app/services/nostr/nostr_logger.dart';
import 'package:ion/app/services/storage/local_storage.c.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -16,20 +19,21 @@ part 'init_provider.c.g.dart';
@Riverpod(keepAlive: true)
Future initApp(Ref ref) async {
- Nostr.initialize();
+ final featureFlagsNotifier = ref.read(featureFlagsProvider.notifier);
+ final logApp = featureFlagsNotifier.get(LoggerFeatureFlag.logApp);
+ final logNostrDart = featureFlagsNotifier.get(LoggerFeatureFlag.logNostrDart);
+
+ if (logApp) Logger.init();
+
+ Nostr.initialize(logNostrDart ? NostrLogger() : null);
await Future.wait([
ref.read(windowManagerProvider.notifier).show(),
- ref.read(envProvider.future),
ref.read(sharedPreferencesProvider.future),
- ]);
-
- await Future.wait([
ref.read(appTemplateProvider.future),
ref.read(authProvider.future),
ref.read(permissionsProvider.notifier).checkAllPermissions(),
ref.read(coinInitializerProvider).initialize(),
+ ref.read(onboardingCompleteProvider.future),
]);
-
- await ref.read(onboardingCompleteProvider.future);
}
diff --git a/lib/app/features/core/views/pages/error_modal.dart b/lib/app/features/core/views/pages/error_modal.dart
index c398a5cbf..66bff2f49 100644
--- a/lib/app/features/core/views/pages/error_modal.dart
+++ b/lib/app/features/core/views/pages/error_modal.dart
@@ -10,10 +10,13 @@ import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/exceptions/exceptions.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/core/providers/env_provider.c.dart';
+import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/generated/assets.gen.dart';
class ErrorModal extends ConsumerWidget {
- const ErrorModal({required this.error, super.key});
+ ErrorModal({required this.error, super.key}) {
+ Logger.error(error);
+ }
final Object error;
diff --git a/lib/app/features/debug/views/debug_page.dart b/lib/app/features/debug/views/debug_page.dart
new file mode 100644
index 000000000..ab259ff8c
--- /dev/null
+++ b/lib/app/features/debug/views/debug_page.dart
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
+import 'package:ion/app/services/logger/logger.dart';
+import 'package:talker_flutter/talker_flutter.dart';
+
+class DebugPage extends ConsumerWidget {
+ const DebugPage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final featureFlags = ref.watch(featureFlagsProvider);
+ final talker = Logger.talker;
+
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ NavigationAppBar.modal(
+ title: const Text('🐞 Debug'),
+ showBackButton: false,
+ actions: const [NavigationCloseButton()],
+ ),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: ListView(
+ shrinkWrap: true,
+ children: [
+ if (talker != null)
+ Card(
+ child: ListTile(
+ leading: const Icon(Icons.bug_report),
+ title: const Text('View Debug Logs'),
+ subtitle: const Text('Check application logs and diagnostics'),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () => Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => TalkerScreen(
+ talker: talker,
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(height: 16.0.s),
+ ExpansionTile(
+ title: const Text('Feature Flags'),
+ children: featureFlags.entries
+ .map(
+ (entry) => Padding(
+ padding: const EdgeInsets.symmetric(vertical: 4),
+ child: ListTile(
+ title: Text(entry.key.key),
+ trailing: Icon(
+ entry.value ? Icons.check_circle : Icons.cancel,
+ color: entry.value ? Colors.green : Colors.red,
+ ),
+ ),
+ ),
+ )
+ .toList(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/app/features/debug/views/debug_shake_gesture.dart b/lib/app/features/debug/views/debug_shake_gesture.dart
new file mode 100644
index 000000000..b09ad0e9b
--- /dev/null
+++ b/lib/app/features/debug/views/debug_shake_gesture.dart
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/features/core/providers/env_provider.c.dart';
+import 'package:ion/app/features/debug/views/debug_page.dart';
+import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart';
+import 'package:shake_gesture/shake_gesture.dart';
+
+class DebugShakeGesture extends HookConsumerWidget {
+ const DebugShakeGesture({
+ required this.child,
+ super.key,
+ });
+
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final showDebugInfo = ref.watch(envProvider.notifier).get(EnvVariable.SHOW_DEBUG_INFO);
+
+ if (!showDebugInfo) return child;
+
+ final isSheetOpen = useState(false);
+
+ return Navigator(
+ onGenerateRoute: (settings) => MaterialPageRoute(
+ builder: (context) => ShakeGesture(
+ onShake: () {
+ if (isSheetOpen.value) return;
+
+ isSheetOpen.value = true;
+ showSimpleBottomSheet(
+ context: context,
+ child: const DebugPage(),
+ onPopInvokedWithResult: (didPop, _) {
+ if (didPop) {
+ isSheetOpen.value = false;
+ }
+ },
+ );
+ },
+ child: child,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/features/feed/views/pages/feed_page/feed_page.dart b/lib/app/features/feed/views/pages/feed_page/feed_page.dart
index dc825f62c..5b9bf01c1 100644
--- a/lib/app/features/feed/views/pages/feed_page/feed_page.dart
+++ b/lib/app/features/feed/views/pages/feed_page/feed_page.dart
@@ -35,7 +35,7 @@ class FeedPage extends HookConsumerWidget {
.select((state) => (state?.hasMore).falseOrValue),
);
final showTrendingVideos =
- ref.read(featureFlagsProvider.notifier).get(FeedFeatureFlag.showTrendingVideo);
+ ref.watch(featureFlagsProvider.notifier).get(FeedFeatureFlag.showTrendingVideo);
useScrollTopOnTabPress(context, scrollController: scrollController);
diff --git a/lib/app/router/components/app_router_builder.dart b/lib/app/router/components/app_router_builder.dart
index f2324016e..e2d42834c 100644
--- a/lib/app/router/components/app_router_builder.dart
+++ b/lib/app/router/components/app_router_builder.dart
@@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/components/global_notification_bar/global_notification_bar.dart';
import 'package:ion/app/components/global_notification_bar/providers/global_notification_provider.c.dart';
import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/debug/views/debug_shake_gesture.dart';
import 'package:ion/app/hooks/use_interval.dart';
import 'package:ion/app/services/ui_event_queue/ui_event_queue_listener.dart';
@@ -41,7 +42,9 @@ class AppRouterBuilder extends HookConsumerWidget {
const GlobalNotificationBar(),
const UiEventQueueListener(),
Expanded(
- child: child ?? const SizedBox.shrink(),
+ child: DebugShakeGesture(
+ child: child ?? const SizedBox.shrink(),
+ ),
),
],
),
diff --git a/lib/app/router/providers/go_router_provider.c.dart b/lib/app/router/providers/go_router_provider.c.dart
index c2f4286da..8cec5edf8 100644
--- a/lib/app/router/providers/go_router_provider.c.dart
+++ b/lib/app/router/providers/go_router_provider.c.dart
@@ -6,15 +6,16 @@ import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/auth/providers/auth_provider.c.dart';
import 'package:ion/app/features/auth/providers/onboarding_complete_provider.c.dart';
import 'package:ion/app/features/auth/views/pages/link_new_device/link_new_device_dialog.dart';
+import 'package:ion/app/features/core/model/feature_flags.dart';
import 'package:ion/app/features/core/permissions/data/models/permissions_types.dart';
import 'package:ion/app/features/core/permissions/providers/permissions_provider.c.dart';
+import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
import 'package:ion/app/features/core/providers/init_provider.c.dart';
import 'package:ion/app/features/core/providers/splash_provider.c.dart';
import 'package:ion/app/features/core/views/pages/error_page.dart';
import 'package:ion/app/features/user/providers/user_metadata_provider.c.dart';
import 'package:ion/app/router/app_router_listenable.dart';
import 'package:ion/app/router/app_routes.c.dart';
-import 'package:ion/app/services/logger/config.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.c.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -49,7 +50,7 @@ GoRouter goRouter(Ref ref) {
routes: $appRoutes,
errorBuilder: (context, state) => ErrorPage(error: state.error ?? Exception('Unknown error')),
initialLocation: SplashRoute().location,
- debugLogDiagnostics: LoggerConfig.routerLogsEnabled,
+ debugLogDiagnostics: ref.read(featureFlagsProvider.notifier).get(LoggerFeatureFlag.logRouters),
navigatorKey: rootNavigatorKey,
);
}
diff --git a/lib/app/router/utils/show_simple_bottom_sheet.dart b/lib/app/router/utils/show_simple_bottom_sheet.dart
index f2ac39432..68466424b 100644
--- a/lib/app/router/utils/show_simple_bottom_sheet.dart
+++ b/lib/app/router/utils/show_simple_bottom_sheet.dart
@@ -9,6 +9,7 @@ Future showSimpleBottomSheet({
required Widget child,
bool useRootNavigator = true,
bool isDismissible = true,
+ PopInvokedWithResultCallback? onPopInvokedWithResult,
}) {
return showModalBottomSheet(
useRootNavigator: useRootNavigator,
@@ -20,6 +21,7 @@ Future showSimpleBottomSheet({
builder: (context) {
return PopScope(
canPop: isDismissible,
+ onPopInvokedWithResult: onPopInvokedWithResult,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
diff --git a/lib/app/services/ion_identity/ion_identity_provider.c.dart b/lib/app/services/ion_identity/ion_identity_provider.c.dart
index 9f0e4f53d..aa2074fc0 100644
--- a/lib/app/services/ion_identity/ion_identity_provider.c.dart
+++ b/lib/app/services/ion_identity/ion_identity_provider.c.dart
@@ -3,8 +3,10 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/features/core/model/feature_flags.dart';
import 'package:ion/app/features/core/providers/env_provider.c.dart';
-import 'package:ion/app/services/logger/config.dart';
+import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
+import 'package:ion/app/services/logger/logger.dart';
import 'package:ion_identity_client/ion_identity.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -12,17 +14,19 @@ part 'ion_identity_provider.c.g.dart';
@Riverpod(keepAlive: true)
Future> ionIdentity(Ref ref) async {
- await ref.watch(envProvider.future);
- final envController = ref.watch(envProvider.notifier);
+ final env = ref.watch(envProvider.notifier);
- final appId = envController.get(
+ final appId = env.get(
Platform.isAndroid ? EnvVariable.ION_ANDROID_APP_ID : EnvVariable.ION_IOS_APP_ID,
);
+ final logIonIdentityClient =
+ ref.read(featureFlagsProvider.notifier).get(LoggerFeatureFlag.logIonIdentityClient);
+
final config = IONIdentityConfig(
appId: appId,
- origin: envController.get(EnvVariable.ION_ORIGIN),
- logging: LoggerConfig.ionIdentityLogsEnabled,
+ origin: env.get(EnvVariable.ION_ORIGIN),
+ logger: logIonIdentityClient ? Logger.talkerDioLogger : null,
);
final ionClient = IONIdentity.createDefault(config: config);
diff --git a/lib/app/services/logger/config.dart b/lib/app/services/logger/config.dart
deleted file mode 100644
index 596d31536..000000000
--- a/lib/app/services/logger/config.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-// SPDX-License-Identifier: ice License 1.0
-
-class LoggerConfig {
- LoggerConfig._();
-
- static const bool riverpodLogsEnabled = false;
-
- static const bool routerLogsEnabled = true;
-
- static const bool nostrLogsEnabled = true;
-
- static const bool ionIdentityLogsEnabled = true;
-
- static const bool dioLogsEnabled = true;
-}
diff --git a/lib/app/services/logger/logger.dart b/lib/app/services/logger/logger.dart
index e5b1fbc2d..165125264 100644
--- a/lib/app/services/logger/logger.dart
+++ b/lib/app/services/logger/logger.dart
@@ -1,16 +1,55 @@
// SPDX-License-Identifier: ice License 1.0
-import 'dart:developer' as developer;
+import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
+import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
+import 'package:talker_flutter/talker_flutter.dart';
class Logger {
Logger._();
+ static Talker? _talker;
+
+ static void init() {
+ _talker = TalkerFlutter.init();
+ }
+
+ static Talker? get talker => _talker;
+
+ static TalkerDioLogger? get talkerDioLogger => TalkerDioLogger(
+ talker: talker,
+ settings: TalkerDioLoggerSettings(
+ printRequestHeaders: true,
+ printResponseHeaders: true,
+ requestPen: AnsiPen()..cyan(),
+ responsePen: AnsiPen()..green(),
+ errorPen: AnsiPen()..red(),
+ ),
+ );
+
static void log(
String message, {
- String name = '',
Object? error,
StackTrace? stackTrace,
}) {
- developer.log(message, name: name, error: error, stackTrace: stackTrace);
+ _talker?.log(message);
+
+ if (error != null) {
+ _talker?.error(error, stackTrace);
+ }
+ }
+
+ static void info(String message) {
+ _talker?.info(message);
+ }
+
+ static void warning(String message) {
+ _talker?.warning(message);
+ }
+
+ static void error(
+ Object error, {
+ StackTrace? stackTrace,
+ }) {
+ _talker?.error(error, stackTrace);
}
}
diff --git a/lib/app/services/nostr/nostr.dart b/lib/app/services/nostr/nostr.dart
index ebc4df7f1..55c464427 100644
--- a/lib/app/services/nostr/nostr.dart
+++ b/lib/app/services/nostr/nostr.dart
@@ -1,17 +1,16 @@
// SPDX-License-Identifier: ice License 1.0
-import 'package:ion/app/services/logger/config.dart';
+import 'package:ion/app/services/nostr/nostr_logger.dart';
import 'package:ion/app/services/nostr/nostr_signature_verifier.dart';
import 'package:nostr_dart/nostr_dart.dart';
class Nostr {
Nostr._();
- static void initialize() {
+ static void initialize(NostrLogger? logger) {
NostrDart.configure(
- // ignore: avoid_redundant_argument_values
- logLevel: LoggerConfig.nostrLogsEnabled ? NostrLogLevel.ALL : NostrLogLevel.OFF,
signatureVerifier: NostrSignatureVerifier(),
+ logger: logger,
);
}
}
diff --git a/lib/app/services/nostr/nostr_logger.dart b/lib/app/services/nostr/nostr_logger.dart
new file mode 100644
index 000000000..d17f2acc0
--- /dev/null
+++ b/lib/app/services/nostr/nostr_logger.dart
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:ion/app/services/logger/logger.dart';
+import 'package:nostr_dart/nostr_dart.dart';
+
+class NostrLogger implements NostrDartLogger {
+ static const _prefix = '🦩 Nostr:';
+
+ @override
+ void info(String message, [Object? error, StackTrace? stackTrace]) {
+ Logger.info('$_prefix $message');
+
+ if (error != null) {
+ Logger.error('$_prefix $error', stackTrace: stackTrace);
+ }
+ }
+
+ @override
+ void warning(String message, [Object? error, StackTrace? stackTrace]) {
+ Logger.warning('$_prefix $message');
+
+ if (error != null) {
+ Logger.error('$_prefix $error', stackTrace: stackTrace);
+ }
+ }
+}
diff --git a/lib/app/services/riverpod/riverpod_logger.dart b/lib/app/services/riverpod/riverpod_logger.dart
deleted file mode 100644
index f3f31ca73..000000000
--- a/lib/app/services/riverpod/riverpod_logger.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-License-Identifier: ice License 1.0
-
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:ion/app/services/logger/logger.dart';
-
-class RiverpodLogger extends ProviderObserver {
- static const String tag = 'Riverpod';
-
- @override
- void didAddProvider(
- ProviderBase