From fffca4a78c3634cb996b45aadcf8f00168d77bc1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 28 Sep 2025 00:39:17 +0800 Subject: [PATCH] :recycle: Refactor logger system --- lib/main.dart | 32 ++--- lib/pods/activity/activity_rpc.dart | 94 +++++---------- lib/pods/activity/ipc_server.dart | 83 ++++++------- lib/pods/chat/call.dart | 4 +- lib/pods/chat/call.g.dart | 2 +- lib/pods/chat/chat_online_count.g.dart | 2 +- lib/pods/chat/chat_subscribe.g.dart | 2 +- lib/pods/chat/messages_notifier.dart | 157 ++++++++----------------- lib/pods/chat/messages_notifier.g.dart | 2 +- lib/pods/config.freezed.dart | 31 ++--- lib/pods/config.g.dart | 2 +- lib/pods/network.dart | 14 ++- lib/pods/userinfo.dart | 11 +- lib/pods/websocket.dart | 16 +-- lib/route.dart | 8 ++ lib/screens/chat/call.dart | 7 +- lib/screens/poll/poll_editor.dart | 7 +- lib/services/notify.universal.dart | 11 +- lib/services/notify.windows.dart | 16 ++- lib/services/update_service.dart | 70 +++++------ lib/talker.dart | 4 + lib/widgets/content/alert.native.dart | 6 +- lib/widgets/content/alert.web.dart | 2 - lib/widgets/content/audio.dart | 7 +- lib/widgets/content/video.native.dart | 6 +- lib/widgets/debug_sheet.dart | 17 +++ lib/widgets/post/compose_recorder.dart | 5 +- lib/widgets/post/compose_shared.dart | 15 ++- 28 files changed, 278 insertions(+), 355 deletions(-) create mode 100644 lib/talker.dart diff --git a/lib/main.dart b/lib/main.dart index 3b400e8b..aafe9fe3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,4 @@ -import 'dart:developer'; import 'dart:io'; - import 'package:croppy/croppy.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:firebase_core/firebase_core.dart'; @@ -12,11 +10,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker_android/image_picker_android.dart'; +import 'package:island/talker.dart'; import 'package:island/firebase_options.dart'; import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/theme.dart'; - import 'package:island/pods/userinfo.dart'; import 'package:island/pods/websocket.dart'; import 'package:island/route.dart'; @@ -28,19 +26,20 @@ import 'package:relative_time/relative_time.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:talker_riverpod_logger/talker_riverpod_logger.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:window_manager/window_manager.dart'; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - log('Handling a background message: ${message.messageId}'); + talker.info('Handling a background message: ${message.messageId}'); } void main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { - log( + talker.info( "[SplashScreen] Keeping the flash screen to loading other resources...", ); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); @@ -73,17 +72,17 @@ void main() async { } } - log("[SplashScreen] Firebase is ready!"); + talker.info("[SplashScreen] Firebase is ready!"); } catch (err) { showErrorAlert(err); } try { - log("[SplashScreen] Loading timezone database..."); + talker.info("[SplashScreen] Loading timezone database..."); await initializeTzdb(); - log("[SplashScreen] Time zone database was loaded!"); + talker.info("[SplashScreen] Time zone database was loaded!"); } catch (err) { - log("[SplashScreen] Failed to load timezone database... $err"); + talker.error("[SplashScreen] Failed to load timezone database... $err"); } final prefs = await SharedPreferences.getInstance(); @@ -106,7 +105,7 @@ void main() async { initialSize = Size(width, height); } } catch (e) { - log("[SplashScreen] Failed to parse saved window size: $e"); + talker.error("[SplashScreen] Failed to parse saved window size: $e"); initialSize = defaultSize; } } @@ -125,7 +124,7 @@ void main() async { await windowManager.focus(); final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0; await windowManager.setOpacity(opacity); - log( + talker.info( "[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}", ); }); @@ -137,16 +136,17 @@ void main() async { if (imagePickerImplementation is ImagePickerAndroid) { imagePickerImplementation.useAndroidPhotoPicker = true; } - log("[SplashScreen] Android image picker is ready!"); + talker.info("[SplashScreen] Android image picker is ready!"); } if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { FlutterNativeSplash.remove(); - log("[SplashScreen] Now hiding the splash screen..."); + talker.info("[SplashScreen] Now hiding the splash screen..."); } runApp( ProviderScope( + observers: [TalkerRiverpodObserver(talker: talker)], overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], child: Directionality( textDirection: TextDirection.ltr, @@ -227,7 +227,9 @@ class IslandApp extends HookConsumerWidget { final onMessageSubscription = FirebaseMessaging.onMessage.listen(( message, ) { - log('Foreground message received: ${message.messageId}'); + talker.info( + '[Notification] foreground message received: ${message.messageId}', + ); handleMessage(message); }); @@ -241,7 +243,7 @@ class IslandApp extends HookConsumerWidget { // Load userinfo final userNotifier = ref.read(userInfoProvider.notifier); ref.listen(websocketStateProvider, (_, state) { - log('[WebSocket] $state'); + talker.info('[WebSocket] $state'); }); Future(() { userNotifier.fetchUser().then((_) { diff --git a/lib/pods/activity/activity_rpc.dart b/lib/pods/activity/activity_rpc.dart index 978d4bdf..b64b1293 100644 --- a/lib/pods/activity/activity_rpc.dart +++ b/lib/pods/activity/activity_rpc.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer' as developer; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/account.dart'; import 'package:island/pods/network.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/account/status.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; @@ -69,13 +69,13 @@ class ActivityRpcServer { // Start WebSocket server while (port <= portRange[1]) { - developer.log('Trying port $port', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Trying port $port'); try { _httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port); - developer.log('Listening on $port', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Listening on $port'); shelf_io.serveRequests(_httpServer!, (Request request) async { - developer.log('New request', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] New request'); if (request.headers['upgrade']?.toLowerCase() == 'websocket') { final handler = webSocketHandler((WebSocketChannel channel, _) { _wsSockets.add(channel); @@ -83,19 +83,16 @@ class ActivityRpcServer { }); return handler(request); } - developer.log( - 'New request disposed due to not websocket', - name: kRpcLogPrefix, - ); + talker.log('New request disposed due to not websocket'); return Response.notFound('Not a WebSocket request'); }); wsSuccess = true; break; } catch (e) { if (e is SocketException && e.osError?.errorCode == 98) { - developer.log('$port in use!', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] $port in use!'); } else { - developer.log('HTTP error: $e', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] HTTP error: $e'); } port++; await Future.delayed(Duration(milliseconds: 100)); // Add delay @@ -121,13 +118,10 @@ class ActivityRpcServer { await _ipcServer!.start(); } catch (e) { - developer.log('IPC server error: $e', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC server error: $e'); } } else { - developer.log( - 'IPC server disabled on macOS or web in production mode', - name: kRpcIpcLogPrefix, - ); + talker.log('IPC server disabled on macOS or web in production mode'); } } @@ -138,7 +132,7 @@ class ActivityRpcServer { try { await socket.sink.close(); } catch (e) { - developer.log('Error closing WebSocket: $e', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Error closing WebSocket: $e'); } } _wsSockets.clear(); @@ -147,7 +141,7 @@ class ActivityRpcServer { // Stop IPC server await _ipcServer?.stop(); - developer.log('Servers stopped', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Servers stopped'); } // Handle new WebSocket connection @@ -159,10 +153,7 @@ class ActivityRpcServer { final clientId = params['client_id'] ?? ''; final origin = request.headers['origin'] ?? ''; - developer.log( - 'New WS connection! origin: $origin, params: $params', - name: kRpcLogPrefix, - ); + talker.log('New WS connection! origin: $origin, params: $params'); if (origin.isNotEmpty && ![ @@ -170,22 +161,19 @@ class ActivityRpcServer { 'https://ptb.discord.com', 'https://canary.discord.com', ].contains(origin)) { - developer.log('Disallowed origin: $origin', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Disallowed origin: $origin'); socket.sink.close(); return; } if (encoding != 'json') { - developer.log( - 'Unsupported encoding requested: $encoding', - name: kRpcLogPrefix, - ); + talker.log('Unsupported encoding requested: $encoding'); socket.sink.close(); return; } if (ver != 1) { - developer.log('Unsupported version requested: $ver', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] Unsupported version requested: $ver'); socket.sink.close(); return; } @@ -195,10 +183,10 @@ class ActivityRpcServer { socket.stream.listen( (data) => _onWsMessage(socketWithMeta, data), onError: (e) { - developer.log('WS socket error: $e', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] WS socket error: $e'); }, onDone: () { - developer.log('WS socket closed', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] WS socket closed'); handlers['close']?.call(socketWithMeta); _wsSockets.remove(socket); }, @@ -210,25 +198,19 @@ class ActivityRpcServer { // Handle incoming WebSocket message Future _onWsMessage(_WsSocketWrapper socket, dynamic data) async { if (data is! String) { - developer.log( - 'Invalid WebSocket message: not a string', - name: kRpcLogPrefix, - ); + talker.log('Invalid WebSocket message: not a string'); return; } try { final jsonData = await compute(jsonDecode, data); if (jsonData is! Map) { - developer.log( - 'Invalid WebSocket message: not a JSON object', - name: kRpcLogPrefix, - ); + talker.log('Invalid WebSocket message: not a JSON object'); return; } - developer.log('WS message: $jsonData', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] WS message: $jsonData'); handlers['message']?.call(socket, jsonData); } catch (e) { - developer.log('WS message parse error: $e', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] WS message parse error: $e'); } } @@ -236,12 +218,12 @@ class ActivityRpcServer { void _handleIpcPacket(IpcSocketWrapper socket, IpcPacket packet) { switch (packet.type) { case IpcTypes.ping: - developer.log('IPC ping received', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC ping received'); socket.sendPong(packet.data); break; case IpcTypes.pong: - developer.log('IPC pong received', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC pong received'); break; case IpcTypes.handshake: @@ -256,7 +238,7 @@ class ActivityRpcServer { if (!socket.handshook) { throw Exception('Need to handshake first'); } - developer.log('IPC frame: ${packet.data}', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC frame: ${packet.data}'); handlers['message']?.call(socket, packet.data); break; @@ -271,22 +253,19 @@ class ActivityRpcServer { // Handle IPC handshake void _onIpcHandshake(IpcSocketWrapper socket, Map params) { - developer.log('IPC handshake: $params', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC handshake: $params'); final ver = int.tryParse(params['v']?.toString() ?? '1') ?? 1; final clientId = params['client_id']?.toString() ?? ''; if (ver != 1) { - developer.log( - 'IPC unsupported version requested: $ver', - name: kRpcIpcLogPrefix, - ); + talker.log('IPC unsupported version requested: $ver'); socket.closeWithCode(IpcErrorCodes.invalidVersion); return; } if (clientId.isEmpty) { - developer.log('IPC client ID required', name: kRpcIpcLogPrefix); + talker.log('[$kRpcLogPrefix] IPC client ID required'); socket.closeWithCode(IpcErrorCodes.invalidClientId); return; } @@ -306,7 +285,7 @@ class _WsSocketWrapper { _WsSocketWrapper(this.channel, this.clientId, this.encoding); void send(Map msg) { - developer.log('WS sending: $msg', name: kRpcLogPrefix); + talker.log('[$kRpcLogPrefix] WS sending: $msg'); channel.sink.add(jsonEncode(msg)); } } @@ -398,12 +377,7 @@ final rpcServerStateProvider = final appId = socket.clientId; final meta = data['args']['activity']; try { - await setRemoteActivityStatus( - ref, - label, - appId, - meta, - ); + await setRemoteActivityStatus(ref, label, appId, meta); final now = DateTime.now(); final status = SnAccountStatus( id: 'local_$appId', @@ -422,10 +396,7 @@ final rpcServerStateProvider = ); ref.read(currentAccountStatusProvider.notifier).setStatus(status); } catch (e) { - developer.log( - 'Failed to set remote activity status: $e', - name: kRpcLogPrefix, - ); + talker.log('Failed to set remote activity status: $e'); } socket.send({ 'cmd': 'SET_ACTIVITY', @@ -442,10 +413,7 @@ final rpcServerStateProvider = await unsetRemoteActivityStatus(ref, appId); ref.read(currentAccountStatusProvider.notifier).clearStatus(); } catch (e) { - developer.log( - 'Failed to unset remote activity status: $e', - name: kRpcLogPrefix, - ); + talker.log('Failed to unset remote activity status: $e'); } }, }); diff --git a/lib/pods/activity/ipc_server.dart b/lib/pods/activity/ipc_server.dart index 5d724dcc..6ca91b97 100644 --- a/lib/pods/activity/ipc_server.dart +++ b/lib/pods/activity/ipc_server.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer' as developer; import 'dart:io'; import 'dart:typed_data'; import 'package:dart_ipc/dart_ipc.dart'; +import 'package:island/talker.dart'; import 'package:path/path.dart' as path; const String kRpcIpcLogPrefix = 'arRPC.ipc'; @@ -128,23 +128,18 @@ class MultiPlatformIpcServer extends IpcServer { @override Future start() async { try { - final ipcPath = Platform.isWindows - ? r'\\.\pipe\discord-ipc-0' - : await _findAvailableUnixIpcPath(); + final ipcPath = + Platform.isWindows + ? r'\\.\pipe\discord-ipc-0' + : await _findAvailableUnixIpcPath(); final serverSocket = await bind(ipcPath); - developer.log( - 'IPC listening at $ipcPath', - name: kRpcIpcLogPrefix, - ); + talker.log('IPC listening at $ipcPath'); _serverSubscription = serverSocket.listen((socket) { final socketWrapper = MultiPlatformIpcSocketWrapper(socket); addSocket(socketWrapper); - developer.log( - 'New IPC connection!', - name: kRpcIpcLogPrefix, - ); + talker.log('New IPC connection!'); _handleIpcData(socketWrapper); }); } catch (e) { @@ -158,7 +153,7 @@ class MultiPlatformIpcServer extends IpcServer { try { socket.close(); } catch (e) { - developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix); + talker.log('Error closing IPC socket: $e'); } } sockets.clear(); @@ -168,31 +163,30 @@ class MultiPlatformIpcServer extends IpcServer { // Handle incoming IPC data void _handleIpcData(MultiPlatformIpcSocketWrapper socket) { final startTime = DateTime.now(); - socket.socket.listen((data) { - final readStart = DateTime.now(); - socket.addData(data); - final readDuration = DateTime.now().difference(readStart).inMicroseconds; - developer.log( - 'Read data took $readDuration microseconds', - name: kRpcIpcLogPrefix, - ); + socket.socket.listen( + (data) { + final readStart = DateTime.now(); + socket.addData(data); + final readDuration = + DateTime.now().difference(readStart).inMicroseconds; + talker.log('Read data took $readDuration microseconds'); - final packets = socket.readPackets(); - for (final packet in packets) { - handlePacket?.call(socket, packet, {}); - } - }, onDone: () { - developer.log('IPC connection closed', name: kRpcIpcLogPrefix); - socket.close(); - }, onError: (e) { - developer.log('IPC data error: $e', name: kRpcIpcLogPrefix); - socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString()); - }); - final totalDuration = DateTime.now().difference(startTime).inMicroseconds; - developer.log( - '_handleIpcData took $totalDuration microseconds', - name: kRpcIpcLogPrefix, + final packets = socket.readPackets(); + for (final packet in packets) { + handlePacket?.call(socket, packet, {}); + } + }, + onDone: () { + talker.log('IPC connection closed'); + socket.close(); + }, + onError: (e) { + talker.log('IPC data error: $e'); + socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString()); + }, ); + final totalDuration = DateTime.now().difference(startTime).inMicroseconds; + talker.log('_handleIpcData took $totalDuration microseconds'); } Future _getMacOsSystemTmpDir() async { @@ -212,10 +206,7 @@ class MultiPlatformIpcServer extends IpcServer { baseDirs.add(macTempDir); } } catch (e) { - developer.log( - 'Failed to get macOS system temp dir: $e', - name: kRpcIpcLogPrefix, - ); + talker.log('Failed to get macOS system temp dir: $e'); } } @@ -241,17 +232,11 @@ class MultiPlatformIpcServer extends IpcServer { try { await File(socketPath).delete(); } catch (_) {} - developer.log( - 'IPC socket will be created at: $socketPath', - name: kRpcIpcLogPrefix, - ); + talker.log('IPC socket will be created at: $socketPath'); return socketPath; } catch (e) { if (i == 0) { - developer.log( - 'IPC path $socketPath not available: $e', - name: kRpcIpcLogPrefix, - ); + talker.log('IPC path $socketPath not available: $e'); } continue; } @@ -271,7 +256,7 @@ class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper { @override void send(Map msg) { - developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix); + talker.log('IPC sending: $msg'); final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg); socket.add(packet); } diff --git a/lib/pods/chat/call.dart b/lib/pods/chat/call.dart index 45a0cd29..8d3f10e4 100644 --- a/lib/pods/chat/call.dart +++ b/lib/pods/chat/call.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; @@ -10,6 +9,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/pods/network.dart'; import 'package:island/models/chat.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:island/talker.dart'; part 'call.g.dart'; part 'call.freezed.dart'; @@ -212,7 +212,7 @@ class CallNotifier extends _$CallNotifier { Future joinRoom(String roomId) async { if (_roomId == roomId && _room != null) { - log('[Call] Call skipped. Already has data'); + talker.info('[Call] Call skipped. Already has data'); return; } else if (_room != null) { if (!_room!.isDisposed && diff --git a/lib/pods/chat/call.g.dart b/lib/pods/chat/call.g.dart index d55185cb..bae843ea 100644 --- a/lib/pods/chat/call.g.dart +++ b/lib/pods/chat/call.g.dart @@ -6,7 +6,7 @@ part of 'call.dart'; // RiverpodGenerator // ************************************************************************** -String _$callNotifierHash() => r'eb9bd41b97e9b5e9d54007c8327edb6567458846'; +String _$callNotifierHash() => r'd374402e51d331cf40724e51fd86bce3c5504776'; /// See also [CallNotifier]. @ProviderFor(CallNotifier) diff --git a/lib/pods/chat/chat_online_count.g.dart b/lib/pods/chat/chat_online_count.g.dart index 3d0705c6..8ce93457 100644 --- a/lib/pods/chat/chat_online_count.g.dart +++ b/lib/pods/chat/chat_online_count.g.dart @@ -7,7 +7,7 @@ part of 'chat_online_count.dart'; // ************************************************************************** String _$chatOnlineCountNotifierHash() => - r'254ed141ffd99585d898203b3d2b86c4d18db80d'; + r'19af8fd0e9f62c65e12a68215406776085235fa3'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/pods/chat/chat_subscribe.g.dart b/lib/pods/chat/chat_subscribe.g.dart index 94d678ba..1e31e484 100644 --- a/lib/pods/chat/chat_subscribe.g.dart +++ b/lib/pods/chat/chat_subscribe.g.dart @@ -7,7 +7,7 @@ part of 'chat_subscribe.dart'; // ************************************************************************** String _$chatSubscribeNotifierHash() => - r'10a6b2c687149ebb419e4c96349d8bab1f183ec6'; + r'df65ecf15d0e97d7e6850ac57b4e681606e77179'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/pods/chat/messages_notifier.dart b/lib/pods/chat/messages_notifier.dart index 48dff854..2c6fb0de 100644 --- a/lib/pods/chat/messages_notifier.dart +++ b/lib/pods/chat/messages_notifier.dart @@ -1,5 +1,4 @@ import "dart:async"; -import "dart:developer" as developer; import "package:dio/dio.dart"; import "package:drift/drift.dart" show Variable; import "package:easy_localization/easy_localization.dart"; @@ -13,6 +12,7 @@ import "package:island/pods/database.dart"; import "package:island/pods/lifecycle.dart"; import "package:island/pods/network.dart"; import "package:island/services/file.dart"; +import "package:island/talker.dart"; import "package:island/widgets/alert.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:uuid/uuid.dart"; @@ -60,10 +60,7 @@ class MessagesNotifier extends _$MessagesNotifier { _identity = identity; } - developer.log( - 'MessagesNotifier built for room $roomId', - name: 'MessagesNotifier', - ); + talker.log('MessagesNotifier built for room $roomId'); // Only setup sync and lifecycle listeners if user is a member if (identity != null) { @@ -71,24 +68,15 @@ class MessagesNotifier extends _$MessagesNotifier { next.whenData((state) { if (state == AppLifecycleState.paused) { _lastPauseTime = DateTime.now(); - developer.log( - 'App paused, recording time', - name: 'MessagesNotifier', - ); + talker.log('App paused, recording time'); } else if (state == AppLifecycleState.resumed) { if (_lastPauseTime != null) { final diff = DateTime.now().difference(_lastPauseTime!); if (diff > const Duration(minutes: 1)) { - developer.log( - 'App resumed after >1 min, syncing messages', - name: 'MessagesNotifier', - ); + talker.log('App resumed after >1 min, syncing messages'); syncMessages(); } else { - developer.log( - 'App resumed within 1 min, skipping sync', - name: 'MessagesNotifier', - ); + talker.log('App resumed within 1 min, skipping sync'); } } } @@ -109,10 +97,7 @@ class MessagesNotifier extends _$MessagesNotifier { int offset = 0, int take = 20, }) async { - developer.log( - 'Getting cached messages from offset $offset, take $take', - name: 'MessagesNotifier', - ); + talker.log('Getting cached messages from offset $offset, take $take'); final List dbMessages; if (_searchQuery != null && _searchQuery!.isNotEmpty) { dbMessages = await _database.searchMessages( @@ -174,10 +159,7 @@ class MessagesNotifier extends _$MessagesNotifier { int offset = 0, int take = 20, }) async { - developer.log( - 'Fetching messages from API, offset $offset, take $take', - name: 'MessagesNotifier', - ); + talker.log('Fetching messages from API, offset $offset, take $take'); if (_totalCount == null) { final response = await _apiClient.get( '/sphere/chat/$_roomId/messages', @@ -221,15 +203,12 @@ class MessagesNotifier extends _$MessagesNotifier { Future syncMessages() async { if (_isSyncing) { - developer.log( - 'Sync already in progress, skipping.', - name: 'MessagesNotifier', - ); + talker.log('Sync already in progress, skipping.'); return; } _isSyncing = true; - developer.log('Starting message sync', name: 'MessagesNotifier'); + talker.log('Starting message sync'); Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true); try { final dbMessages = await _database.getMessagesForRoom( @@ -243,10 +222,7 @@ class MessagesNotifier extends _$MessagesNotifier { : _database.companionToMessage(dbMessages.first); if (lastMessage == null) { - developer.log( - 'No local messages, fetching from network', - name: 'MessagesNotifier', - ); + talker.log('No local messages, fetching from network'); final newMessages = await _fetchAndCacheMessages( offset: 0, take: _pageSize, @@ -264,10 +240,7 @@ class MessagesNotifier extends _$MessagesNotifier { ); final response = MessageSyncResponse.fromJson(resp.data); - developer.log( - 'Sync response: ${response.messages.length} changes', - name: 'MessagesNotifier', - ); + talker.log('Sync response: ${response.messages.length} changes'); for (final message in response.messages) { switch (message.type) { case "messages.update": @@ -282,15 +255,14 @@ class MessagesNotifier extends _$MessagesNotifier { await receiveMessage(message); } } catch (err, stackTrace) { - developer.log( + talker.log( 'Error syncing messages', - name: 'MessagesNotifier', - error: err, + exception: err, stackTrace: stackTrace, ); showErrorAlert(err); } finally { - developer.log('Finished message sync', name: 'MessagesNotifier'); + talker.log('Finished message sync'); Future.microtask( () => ref.read(isSyncingProvider.notifier).state = false, ); @@ -340,7 +312,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future loadInitial() async { - developer.log('Loading initial messages', name: 'MessagesNotifier'); + talker.log('Loading initial messages'); if (_searchQuery == null || _searchQuery!.isEmpty) { syncMessages(); } @@ -354,7 +326,7 @@ class MessagesNotifier extends _$MessagesNotifier { Future loadMore() async { if (!_hasMore || state is AsyncLoading) return; - developer.log('Loading more messages', name: 'MessagesNotifier'); + talker.log('Loading more messages'); try { final currentMessages = state.value ?? []; @@ -370,10 +342,10 @@ class MessagesNotifier extends _$MessagesNotifier { _sortMessages([...currentMessages, ...newMessages]), ); } catch (err, stackTrace) { - developer.log( + talker.log( 'Error loading more messages', - name: 'MessagesNotifier', - error: err, + + exception: err, stackTrace: stackTrace, ); showErrorAlert(err); @@ -389,10 +361,7 @@ class MessagesNotifier extends _$MessagesNotifier { Function(String, Map)? onProgress, }) async { final nonce = const Uuid().v4(); - developer.log( - 'Sending message with nonce $nonce', - name: 'MessagesNotifier', - ); + talker.log('Sending message with nonce $nonce'); final baseUrl = ref.read(serverUrlProvider); final token = await getToken(ref.watch(tokenProvider)); if (token == null) throw ArgumentError('Access token is null'); @@ -496,15 +465,12 @@ class MessagesNotifier extends _$MessagesNotifier { }).toList(); state = AsyncValue.data(newMessages); } - developer.log( - 'Message with nonce $nonce sent successfully', - name: 'MessagesNotifier', - ); + talker.log('Message with nonce $nonce sent successfully'); } catch (e, stackTrace) { - developer.log( + talker.log( 'Failed to send message with nonce $nonce', - name: 'MessagesNotifier', - error: e, + + exception: e, stackTrace: stackTrace, ); localMessage.status = MessageStatus.failed; @@ -526,10 +492,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future retryMessage(String pendingMessageId) async { - developer.log( - 'Retrying message $pendingMessageId', - name: 'MessagesNotifier', - ); + talker.log('Retrying message $pendingMessageId'); final message = await fetchMessageById(pendingMessageId); if (message == null) { throw Exception('Message not found'); @@ -573,10 +536,10 @@ class MessagesNotifier extends _$MessagesNotifier { }).toList(); state = AsyncValue.data(newMessages); } catch (e, stackTrace) { - developer.log( + talker.log( 'Failed to retry message $pendingMessageId', - name: 'MessagesNotifier', - error: e, + + exception: e, stackTrace: stackTrace, ); message.status = MessageStatus.failed; @@ -599,10 +562,7 @@ class MessagesNotifier extends _$MessagesNotifier { Future receiveMessage(SnChatMessage remoteMessage) async { if (remoteMessage.chatRoomId != _roomId) return; - developer.log( - 'Received new message ${remoteMessage.id}', - name: 'MessagesNotifier', - ); + talker.log('Received new message ${remoteMessage.id}'); final localMessage = LocalChatMessage.fromRemoteMessage( remoteMessage, @@ -647,10 +607,7 @@ class MessagesNotifier extends _$MessagesNotifier { Future receiveMessageUpdate(SnChatMessage remoteMessage) async { if (remoteMessage.chatRoomId != _roomId) return; - developer.log( - 'Received message update ${remoteMessage.id}', - name: 'MessagesNotifier', - ); + talker.log('Received message update ${remoteMessage.id}'); final targetId = remoteMessage.meta['message_id'] ?? remoteMessage.id; final updatedMessage = LocalChatMessage.fromRemoteMessage( @@ -673,10 +630,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future receiveMessageDeletion(String messageId) async { - developer.log( - 'Received message deletion $messageId', - name: 'MessagesNotifier', - ); + talker.log('Received message deletion $messageId'); _pendingMessages.remove(messageId); final currentMessages = state.value ?? []; @@ -713,15 +667,15 @@ class MessagesNotifier extends _$MessagesNotifier { } Future deleteMessage(String messageId) async { - developer.log('Deleting message $messageId', name: 'MessagesNotifier'); + talker.log('Deleting message $messageId'); try { await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId'); await receiveMessageDeletion(messageId); } catch (err, stackTrace) { - developer.log( + talker.log( 'Error deleting message $messageId', - name: 'MessagesNotifier', - error: err, + + exception: err, stackTrace: stackTrace, ); showErrorAlert(err); @@ -743,10 +697,7 @@ class MessagesNotifier extends _$MessagesNotifier { } Future fetchMessageById(String messageId) async { - developer.log( - 'Fetching message by id $messageId', - name: 'MessagesNotifier', - ); + talker.log('Fetching message by id $messageId'); try { final localMessage = await (_database.select(_database.chatMessages) @@ -773,24 +724,18 @@ class MessagesNotifier extends _$MessagesNotifier { } Future jumpToMessage(String messageId) async { - developer.log( - 'Starting jump to message $messageId', - name: 'MessagesNotifier', - ); + talker.log('Starting jump to message $messageId'); if (_isJumping) { - developer.log( - 'Jump already in progress, skipping', - name: 'MessagesNotifier', - ); + talker.log('Jump already in progress, skipping'); return -1; } _isJumping = true; try { - developer.log('Fetching message $messageId', name: 'MessagesNotifier'); + talker.log('Fetching message $messageId'); final message = await fetchMessageById(messageId); if (message == null) { - developer.log('Message $messageId not found', name: 'MessagesNotifier'); + talker.log('Message $messageId not found'); showSnackBar('messageNotFound'.tr()); return -1; } @@ -801,16 +746,14 @@ class MessagesNotifier extends _$MessagesNotifier { (m) => m.id == messageId, ); if (existingIndex >= 0) { - developer.log( + talker.log( 'Message $messageId already in current state at index $existingIndex, jumping directly', - name: 'MessagesNotifier', ); return existingIndex; } - developer.log( + talker.log( 'Message $messageId not in current state, loading messages around it', - name: 'MessagesNotifier', ); // Count messages newer than this one @@ -828,10 +771,7 @@ class MessagesNotifier extends _$MessagesNotifier { // Load messages around this position final offset = (newerCount - _pageSize ~/ 2).clamp(0, double.infinity).toInt(); - developer.log( - 'Loading messages with offset $offset, take $_pageSize', - name: 'MessagesNotifier', - ); + talker.log('Loading messages with offset $offset, take $_pageSize'); final loadedMessages = await _getCachedMessages( offset: offset, take: _pageSize, @@ -841,9 +781,8 @@ class MessagesNotifier extends _$MessagesNotifier { final currentIds = currentMessages.map((m) => m.id).toSet(); final newMessages = loadedMessages.where((m) => !currentIds.contains(m.id)).toList(); - developer.log( + talker.log( 'Loaded ${loadedMessages.length} messages, ${newMessages.length} are new', - name: 'MessagesNotifier', ); if (newMessages.isNotEmpty) { @@ -858,19 +797,15 @@ class MessagesNotifier extends _$MessagesNotifier { } _sortMessages(uniqueMessages); state = AsyncValue.data(uniqueMessages); - developer.log( + talker.log( 'Updated state with ${uniqueMessages.length} total messages', - name: 'MessagesNotifier', ); } final finalIndex = (state.value ?? []).indexWhere( (m) => m.id == messageId, ); - developer.log( - 'Final index for message $messageId is $finalIndex', - name: 'MessagesNotifier', - ); + talker.log('Final index for message $messageId is $finalIndex'); return finalIndex; } finally { _isJumping = false; diff --git a/lib/pods/chat/messages_notifier.g.dart b/lib/pods/chat/messages_notifier.g.dart index 1b200257..03f6f334 100644 --- a/lib/pods/chat/messages_notifier.g.dart +++ b/lib/pods/chat/messages_notifier.g.dart @@ -6,7 +6,7 @@ part of 'messages_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$messagesNotifierHash() => r'4257c9b3792418e913d0bac3ef58e727314635af'; +String _$messagesNotifierHash() => r'3aad1491b777570913f3867abd280fa59949b1f1'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/pods/config.freezed.dart b/lib/pods/config.freezed.dart index 773c18de..7f8a99d6 100644 --- a/lib/pods/config.freezed.dart +++ b/lib/pods/config.freezed.dart @@ -63,7 +63,9 @@ class _$AppSettingsCopyWithImpl<$Res> final AppSettings _self; final $Res Function(AppSettings) _then; -@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = null,}) { +/// Create a copy of AppSettings +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,}) { return _then(_self.copyWith( autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable @@ -78,7 +80,8 @@ as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ign as Size?,windowOpacity: null == windowOpacity ? _self.windowOpacity : windowOpacity // ignore: cast_nullable_to_non_nullable as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable -as String, +as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -160,10 +163,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle, String? themeMode)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppSettings() when $default != null: -return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);case _: +return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode);case _: return orElse(); } @@ -181,10 +184,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle, String? themeMode) $default,) {final _that = this; switch (_that) { case _AppSettings(): -return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);} +return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode);} } /// A variant of `when` that fallback to returning `null` /// @@ -198,10 +201,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle, String? themeMode)? $default,) {final _that = this; switch (_that) { case _AppSettings() when $default != null: -return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle);case _: +return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.windowOpacity,_that.defaultPoolId,_that.messageDisplayStyle,_that.themeMode);case _: return null; } @@ -244,16 +247,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_ @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.windowOpacity, windowOpacity) || other.windowOpacity == windowOpacity)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)&&(identical(other.themeMode, themeMode) || other.themeMode == themeMode)); } @override -int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle); +int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle,themeMode); @override String toString() { - return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)'; + return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode)'; } @@ -281,7 +284,7 @@ class __$AppSettingsCopyWithImpl<$Res> /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = freezed,}) { return _then(_AppSettings( autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable @@ -296,8 +299,8 @@ as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ign as Size?,windowOpacity: null == windowOpacity ? _self.windowOpacity : windowOpacity // ignore: cast_nullable_to_non_nullable as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable -as String,themeMode: null == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable -as String, +as String,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable +as String?, )); } diff --git a/lib/pods/config.g.dart b/lib/pods/config.g.dart index b461eabc..91faebb3 100644 --- a/lib/pods/config.g.dart +++ b/lib/pods/config.g.dart @@ -7,7 +7,7 @@ part of 'config.dart'; // ************************************************************************** String _$appSettingsNotifierHash() => - r'3b0967a39a375c664c3fd44cbee7936b8b2f5fec'; + r'c3042f77067b5f36102f17277e174a756121ac74'; /// See also [AppSettingsNotifier]. @ProviderFor(AppSettingsNotifier) diff --git a/lib/pods/network.dart b/lib/pods/network.dart index a71916bd..0afbd9c6 100644 --- a/lib/pods/network.dart +++ b/lib/pods/network.dart @@ -10,6 +10,8 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:island/models/auth.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:talker_dio_logger/talker_dio_logger.dart'; +import 'package:island/talker.dart'; import 'config.dart'; @@ -75,7 +77,7 @@ final apiClientProvider = Provider((ref) { ), ); - dio.interceptors.add( + dio.interceptors.addAll([ InterceptorsWrapper( onRequest: ( RequestOptions options, @@ -97,7 +99,15 @@ final apiClientProvider = Provider((ref) { return handler.next(options); }, ), - ); + TalkerDioLogger( + talker: talker, + settings: const TalkerDioLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ), + ), + ]); return dio; }); diff --git a/lib/pods/userinfo.dart b/lib/pods/userinfo.dart index e3b21e4f..23cb1b93 100644 --- a/lib/pods/userinfo.dart +++ b/lib/pods/userinfo.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'dart:io' show Platform; import 'package:dio/dio.dart'; @@ -12,6 +11,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/account.dart'; import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; +import 'package:island/talker.dart'; class UserInfoNotifier extends StateNotifier> { final Ref _ref; @@ -21,7 +21,7 @@ class UserInfoNotifier extends StateNotifier> { Future fetchUser() async { final token = _ref.watch(tokenProvider); if (token == null) { - log('[UserInfo] No token found, not going to fetch...'); + talker.info('[UserInfo] No token found, not going to fetch...'); return; } try { @@ -75,11 +75,10 @@ class UserInfoNotifier extends StateNotifier> { } }); } - log( + talker.error( "[UserInfo] Failed to fetch user info...", - name: 'UserInfoNotifier', - error: error, - stackTrace: stackTrace, + error, + stackTrace, ); state = AsyncValue.data(null); } diff --git a/lib/pods/websocket.dart b/lib/pods/websocket.dart index 6fcdeabf..0f57792e 100644 --- a/lib/pods/websocket.dart +++ b/lib/pods/websocket.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -8,6 +7,7 @@ import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:island/talker.dart'; part 'websocket.freezed.dart'; part 'websocket.g.dart'; @@ -64,7 +64,7 @@ class WebSocketService { final url = '$baseUrl/ws'.replaceFirst('http', 'ws'); - log('[WebSocket] Trying connecting to $url'); + talker.info('[WebSocket] Trying connecting to $url'); try { if (kIsWeb) { _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token')); @@ -88,24 +88,24 @@ class WebSocketService { return; } _streamController.sink.add(packet); - log( + talker.info( "[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}", ); if (packet.type == 'pong' && _heartbeatAt != null) { var now = DateTime.now(); heartbeatDelay = now.difference(_heartbeatAt!); - log( + talker.info( "[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms", ); } }, onDone: () { - log('[WebSocket] Connection closed, attempting to reconnect...'); + talker.info('[WebSocket] Connection closed, attempting to reconnect...'); _scheduleReconnect(); _statusStreamController.sink.add(WebSocketState.disconnected()); }, onError: (error) { - log('[WebSocket] Error occurred: $error, attempting to reconnect...'); + talker.error('[WebSocket] Error occurred: $error, attempting to reconnect...'); _scheduleReconnect(); _statusStreamController.sink.add( WebSocketState.error(error.toString()), @@ -113,7 +113,7 @@ class WebSocketService { }, ); } catch (err) { - log('[WebSocket] Failed to connect: $err'); + talker.error('[WebSocket] Failed to connect: $err'); _scheduleReconnect(); } } @@ -135,7 +135,7 @@ class WebSocketService { void _beatTheHeart() { _heartbeatAt = DateTime.now(); - log('[WebSocket] We\'re beating the heart! $_heartbeatAt'); + talker.info('[WebSocket] We\'re beating the heart! $_heartbeatAt'); sendMessage(jsonEncode(WebSocketPacket(type: 'ping', data: null))); } diff --git a/lib/route.dart b/lib/route.dart index 1261f74e..9fc0bc69 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -64,7 +64,9 @@ import 'package:island/screens/account/event_calendar.dart'; import 'package:island/screens/discovery/realms.dart'; import 'package:island/screens/reports/report_detail.dart'; import 'package:island/screens/reports/report_list.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/post/post_shuffle.dart'; +import 'package:talker_flutter/talker_flutter.dart'; // Shell route keys for nested navigation final rootNavigatorKey = GlobalKey(); @@ -96,6 +98,7 @@ final routerProvider = Provider((ref) { observers: [ if (_supportsAnalytics) FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), + TalkerRouteObserver(talker), ], routes: [ ShellRoute( @@ -132,6 +135,11 @@ final routerProvider = Provider((ref) { return CallScreen(roomId: id); }, ), + GoRoute( + name: 'logs', + path: '/logs', + builder: (context, state) => TalkerScreen(talker: talker), + ), GoRoute( name: 'accountCalendar', path: '/account/:name/calendar', diff --git a/lib/screens/chat/call.dart b/lib/screens/chat/call.dart index 7c8d0c42..dc1ce75a 100644 --- a/lib/screens/chat/call.dart +++ b/lib/screens/chat/call.dart @@ -1,11 +1,10 @@ -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart' hide ConnectionState; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/chat/call.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/chat/call_button.dart'; import 'package:island/widgets/chat/call_overlay.dart'; @@ -26,14 +25,14 @@ class CallScreen extends HookConsumerWidget { final callNotifier = ref.watch(callNotifierProvider.notifier); useEffect(() { - log('[Call] Joining the call...'); + talker.info('[Call] Joining the call...'); callNotifier.joinRoom(roomId).catchError((_) { showConfirmAlert( 'Seems there already has a call connected, do you want override it?', 'Call already connected', ).then((value) { if (value != true) return; - log('[Call] Joining the call... with overrides'); + talker.info('[Call] Joining the call... with overrides'); callNotifier.disconnect(); callNotifier.dispose(); callNotifier.joinRoom(roomId); diff --git a/lib/screens/poll/poll_editor.dart b/lib/screens/poll/poll_editor.dart index 3a7e0245..f11ead30 100644 --- a/lib/screens/poll/poll_editor.dart +++ b/lib/screens/poll/poll_editor.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; import 'package:gap/gap.dart'; import 'package:island/pods/network.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/models/poll.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -92,10 +91,10 @@ class PollEditor extends Notifier { questions: poll.questions, ); } on DioException catch (e) { - log('Failed to load poll $id: ${e.message}'); + talker.error('Failed to load poll $id: ${e.message}'); // Keep state with id set; UI may handle error display. } catch (e) { - log('Unexpected error loading poll $id: $e'); + talker.error('Unexpected error loading poll $id: $e'); } finally { if (context.mounted) hideLoadingModal(context); } diff --git a/lib/services/notify.universal.dart b/lib/services/notify.universal.dart index eeac0bef..6c991f51 100644 --- a/lib/services/notify.universal.dart +++ b/lib/services/notify.universal.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; - import 'package:dio/dio.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; @@ -13,6 +11,7 @@ import 'package:island/main.dart'; import 'package:island/route.dart'; import 'package:island/models/account.dart'; import 'package:island/pods/websocket.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/app_notification.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -96,7 +95,7 @@ StreamSubscription setupNotificationListener( final notification = SnNotification.fromJson(pkt.data!); if (_appLifecycleState == AppLifecycleState.resumed) { // App is focused, show in-app notification - log( + talker.info( '[Notification] Showing in-app notification: ${notification.title}', ); showTopSnackBar( @@ -142,7 +141,7 @@ StreamSubscription setupNotificationListener( } else { // App is in background, show system notification (only on supported platforms) if (!kIsWeb && !Platform.isIOS) { - log( + talker.info( '[Notification] Showing system notification: ${notification.title}', ); @@ -167,7 +166,7 @@ StreamSubscription setupNotificationListener( payload: notification.meta['action_uri'] as String?, ); } else { - log( + talker.info( '[Notification] Skipping system notification for unsupported platform: ${notification.title}', ); } @@ -206,7 +205,7 @@ Future subscribePushNotification( _putTokenToRemote(apiClient, fcmToken, 1); }) .onError((err) { - log("Failed to get firebase cloud messaging push token: $err"); + talker.error("Failed to get firebase cloud messaging push token: $err"); }); if (deviceToken != null) { diff --git a/lib/services/notify.windows.dart b/lib/services/notify.windows.dart index 379e3bb8..5a5a4c68 100644 --- a/lib/services/notify.windows.dart +++ b/lib/services/notify.windows.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; - import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -11,17 +9,17 @@ import 'package:island/main.dart'; import 'package:island/route.dart'; import 'package:island/models/account.dart'; import 'package:island/pods/websocket.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/app_notification.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import 'package:windows_notification/windows_notification.dart' - as windows_notification; +import 'package:windows_notification/windows_notification.dart' as winty; import 'package:windows_notification/notification_message.dart'; import 'package:dio/dio.dart'; // Windows notification instance -windows_notification.WindowsNotification? windowsNotification; +winty.WindowsNotification? windowsNotification; AppLifecycleState _appLifecycleState = AppLifecycleState.resumed; @@ -31,7 +29,7 @@ void _onAppLifecycleChanged(AppLifecycleState state) { Future initializeLocalNotifications() async { // Initialize Windows notification for Windows platform - windowsNotification = windows_notification.WindowsNotification( + windowsNotification = winty.WindowsNotification( applicationId: 'dev.solsynth.solian', ); @@ -61,7 +59,7 @@ StreamSubscription setupNotificationListener( final notification = SnNotification.fromJson(pkt.data!); if (_appLifecycleState == AppLifecycleState.resumed) { // App is focused, show in-app notification - log( + talker.info( '[Notification] Showing in-app notification: ${notification.title}', ); showTopSnackBar( @@ -99,7 +97,7 @@ StreamSubscription setupNotificationListener( ); } else { // App is in background, show Windows system notification - log( + talker.info( '[Notification] Showing Windows system notification: ${notification.title}', ); @@ -150,7 +148,7 @@ Future subscribePushNotification( _putTokenToRemote(apiClient, fcmToken, 1); }) .onError((err) { - log("Failed to get firebase cloud messaging push token: $err"); + talker.error("Failed to get firebase cloud messaging push token: $err"); }); if (deviceToken != null) { diff --git a/lib/services/update_service.dart b/lib/services/update_service.dart index d2055ec4..764d7a9f 100644 --- a/lib/services/update_service.dart +++ b/lib/services/update_service.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; import 'package:archive/archive.dart'; @@ -19,6 +18,7 @@ import 'package:collection/collection.dart'; // Added for firstWhereOrNull import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:island/widgets/content/sheet.dart'; +import 'package:island/talker.dart'; /// Data model for a GitHub release we care about class GithubReleaseInfo { @@ -121,40 +121,40 @@ class UpdateService { /// Checks GitHub for the latest release and compares against the current app version. /// If update is available, shows a bottom sheet with changelog and an action to open release page. Future checkForUpdates(BuildContext context) async { - log('[Update] Checking for updates...'); + talker.info('[Update] Checking for updates...'); try { final release = await fetchLatestRelease(); if (release == null) { - log('[Update] No latest release found or could not fetch.'); + talker.info('[Update] No latest release found or could not fetch.'); return; } - log('[Update] Fetched latest release: ${release.tagName}'); + talker.info('[Update] Fetched latest release: ${release.tagName}'); final info = await PackageInfo.fromPlatform(); final localVersionStr = '${info.version}+${info.buildNumber}'; - log('[Update] Local app version: $localVersionStr'); + talker.info('[Update] Local app version: $localVersionStr'); final latest = _ParsedVersion.tryParse(release.tagName); final local = _ParsedVersion.tryParse(localVersionStr); if (latest == null || local == null) { - log( + talker.info( '[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr', ); // If parsing fails, do nothing silently return; } - log('[Update] Parsed versions. Latest: $latest, Local: $local'); + talker.info('[Update] Parsed versions. Latest: $latest, Local: $local'); final needsUpdate = latest.compareTo(local) > 0; if (!needsUpdate) { - log('[Update] App is up to date. No update needed.'); + talker.info('[Update] App is up to date. No update needed.'); return; } - log('[Update] Update available! Latest: $latest, Local: $local'); + talker.info('[Update] Update available! Latest: $latest, Local: $local'); if (!context.mounted) { - log('[Update] Context not mounted, cannot show update sheet.'); + talker.info('[Update] Context not mounted, cannot show update sheet.'); return; } @@ -163,10 +163,10 @@ class UpdateService { if (context.mounted) { await showUpdateSheet(context, release); - log('[Update] Update sheet shown.'); + talker.info('[Update] Update sheet shown.'); } } catch (e) { - log('[Update] Error checking for updates: $e'); + talker.error('[Update] Error checking for updates: $e'); // Ignore errors (network, api, etc.) return; } @@ -262,18 +262,18 @@ class UpdateService { ? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}' : _releasesLatestApi; - log( + talker.info( '[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)', ); final resp = await _dio.get(apiEndpoint); if (resp.statusCode != 200) { - log( + talker.error( '[Update] Failed to fetch latest release. Status code: ${resp.statusCode}', ); return null; } final data = resp.data as Map; - log('[Update] Successfully fetched release data.'); + talker.info('[Update] Successfully fetched release data.'); final tagName = (data['tag_name'] ?? '').toString(); final name = (data['name'] ?? tagName).toString(); @@ -288,13 +288,13 @@ class UpdateService { []; if (tagName.isEmpty || htmlUrl.isEmpty) { - log( + talker.error( '[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"', ); return null; } - log('[Update] Returning GithubReleaseInfo for tag: $tagName'); + talker.info('[Update] Returning GithubReleaseInfo for tag: $tagName'); return GithubReleaseInfo( tagName: tagName, name: name, @@ -380,7 +380,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { await File(zipPath).delete(); await Directory(extractDir).delete(recursive: true); } catch (e) { - log('[Update] Error cleaning up temporary files: $e'); + talker.error('[Update] Error cleaning up temporary files: $e'); } } catch (e) { _showError('Update failed: $e'); @@ -437,7 +437,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { void Function(int received, int total)? onProgress, }) async { try { - log('[Update] Starting Windows installer download from: $url'); + talker.info('[Update] Starting Windows installer download from: $url'); final tempDir = await getTemporaryDirectory(); final fileName = @@ -449,7 +449,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { filePath, onReceiveProgress: (received, total) { if (total != -1) { - log( + talker.info( '[Update] Download progress: ${(received / total * 100).toStringAsFixed(1)}%', ); } @@ -458,16 +458,16 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { ); if (response.statusCode == 200) { - log('[Update] Windows installer downloaded successfully to: $filePath'); + talker.info('[Update] Windows installer downloaded successfully to: $filePath'); return filePath; } else { - log( + talker.error( '[Update] Failed to download Windows installer. Status: ${response.statusCode}', ); return null; } } catch (e) { - log('[Update] Error downloading Windows installer: $e'); + talker.error('[Update] Error downloading Windows installer: $e'); return null; } } @@ -475,7 +475,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { /// Extracts the ZIP file to a temporary directory Future _extractWindowsInstaller(String zipPath) async { try { - log('[Update] Extracting Windows installer from: $zipPath'); + talker.info('[Update] Extracting Windows installer from: $zipPath'); final tempDir = await getTemporaryDirectory(); final extractDir = path.join( @@ -500,10 +500,10 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { } } - log('[Update] Windows installer extracted successfully to: $extractDir'); + talker.info('[Update] Windows installer extracted successfully to: $extractDir'); return extractDir; } catch (e) { - log('[Update] Error extracting Windows installer: $e'); + talker.error('[Update] Error extracting Windows installer: $e'); return null; } } @@ -511,7 +511,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { /// Runs the setup.exe file Future _runWindowsInstaller(String extractDir) async { try { - log('[Update] Running Windows installer from: $extractDir'); + talker.info('[Update] Running Windows installer from: $extractDir'); final dir = Directory(extractDir); final exeFiles = dir @@ -520,30 +520,30 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> { .toList(); if (exeFiles.isEmpty) { - log('[Update] No .exe file found in extracted directory'); + talker.info('[Update] No .exe file found in extracted directory'); return false; } final setupExePath = exeFiles.first.path; - log('[Update] Found installer executable: $setupExePath'); + talker.info('[Update] Found installer executable: $setupExePath'); final shell = Shell(); final results = await shell.run(setupExePath); final result = results.first; if (result.exitCode == 0) { - log('[Update] Windows installer completed successfully'); + talker.info('[Update] Windows installer completed successfully'); return true; } else { - log( + talker.error( '[Update] Windows installer failed with exit code: ${result.exitCode}', ); - log('[Update] Installer output: ${result.stdout}'); - log('[Update] Installer errors: ${result.stderr}'); + talker.error('[Update] Installer output: ${result.stdout}'); + talker.error('[Update] Installer errors: ${result.stderr}'); return false; } } catch (e) { - log('[Update] Error running Windows installer: $e'); + talker.error('[Update] Error running Windows installer: $e'); return false; } } @@ -651,7 +651,7 @@ class _UpdateSheetState extends State<_UpdateSheet> { Expanded( child: FilledButton.icon( onPressed: () { - log(widget.androidUpdateUrl!); + talker.info(widget.androidUpdateUrl!); _installUpdate(widget.androidUpdateUrl!); }, icon: const Icon(Symbols.update), diff --git a/lib/talker.dart b/lib/talker.dart new file mode 100644 index 00000000..3a4b0b56 --- /dev/null +++ b/lib/talker.dart @@ -0,0 +1,4 @@ + +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); diff --git a/lib/widgets/content/alert.native.dart b/lib/widgets/content/alert.native.dart index fcd6565c..fb536b9b 100644 --- a/lib/widgets/content/alert.native.dart +++ b/lib/widgets/content/alert.native.dart @@ -1,11 +1,9 @@ -import 'dart:developer'; - import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_platform_alert/flutter_platform_alert.dart'; +import 'package:island/talker.dart'; String _parseRemoteError(DioException err) { - log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); String? message; if (err.response?.data is String) { message = err.response?.data; @@ -30,7 +28,7 @@ String _parseRemoteError(DioException err) { void showErrorAlert(dynamic err) async { if (err is Error) { - log('${err.stackTrace}'); + talker.error('Something went wrong...', err, err.stackTrace); } final text = switch (err) { String _ => err, diff --git a/lib/widgets/content/alert.web.dart b/lib/widgets/content/alert.web.dart index b6a61f6c..397e719f 100644 --- a/lib/widgets/content/alert.web.dart +++ b/lib/widgets/content/alert.web.dart @@ -1,13 +1,11 @@ // ignore_for_file: avoid_web_libraries_in_flutter -import 'dart:developer'; import 'dart:js' as js; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; String _parseRemoteError(DioException err) { - log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}'); String? message; if (err.response?.data is String) { message = err.response?.data; diff --git a/lib/widgets/content/audio.dart b/lib/widgets/content/audio.dart index 8e459d9f..48fbb544 100644 --- a/lib/widgets/content/audio.dart +++ b/lib/widgets/content/audio.dart @@ -1,11 +1,10 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/network.dart'; import 'package:island/services/time.dart'; +import 'package:island/talker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:media_kit/media_kit.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -57,7 +56,7 @@ class _UniversalAudioState extends ConsumerState { String? uri; final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); if (inCacheInfo == null) { - log('[MediaPlayer] Miss cache: $url'); + talker.info('[MediaPlayer] Miss cache: $url'); final token = ref.watch(tokenProvider)?.token; DefaultCacheManager().downloadFile( url, @@ -66,7 +65,7 @@ class _UniversalAudioState extends ConsumerState { uri = url; } else { uri = inCacheInfo.file.path; - log('[MediaPlayer] Hit cache: $url'); + talker.info('[MediaPlayer] Hit cache: $url'); } _player!.open(Media(uri), play: widget.autoplay); diff --git a/lib/widgets/content/video.native.dart b/lib/widgets/content/video.native.dart index df770943..dc47a694 100644 --- a/lib/widgets/content/video.native.dart +++ b/lib/widgets/content/video.native.dart @@ -1,10 +1,10 @@ -import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:island/pods/network.dart'; +import 'package:island/talker.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; @@ -37,7 +37,7 @@ class _UniversalVideoState extends ConsumerState { String? uri; final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); if (inCacheInfo == null) { - log('[MediaPlayer] Miss cache: $url'); + talker.info('[MediaPlayer] Miss cache: $url'); final token = ref.watch(tokenProvider)?.token; DefaultCacheManager().downloadFile( url, @@ -46,7 +46,7 @@ class _UniversalVideoState extends ConsumerState { uri = url; } else { uri = inCacheInfo.file.path; - log('[MediaPlayer] Hit cache: $url'); + talker.info('[MediaPlayer] Hit cache: $url'); } _player!.open(Media(uri), play: widget.autoplay); diff --git a/lib/widgets/debug_sheet.dart b/lib/widgets/debug_sheet.dart index e96c6338..a211a3ed 100644 --- a/lib/widgets/debug_sheet.dart +++ b/lib/widgets/debug_sheet.dart @@ -12,6 +12,8 @@ import 'package:island/widgets/content/network_status_sheet.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:island/pods/config.dart'; +import 'package:talker_flutter/talker_flutter.dart'; +import 'package:island/talker.dart'; Future _showSetTokenDialog(BuildContext context, WidgetRef ref) async { final TextEditingController controller = TextEditingController(); @@ -115,6 +117,21 @@ class DebugSheet extends HookConsumerWidget { }, ), const Divider(height: 8), + ListTile( + minTileHeight: 48, + leading: const Icon(Symbols.bug_report), + trailing: const Icon(Symbols.chevron_right), + title: Text('Logs'), + contentPadding: EdgeInsets.symmetric(horizontal: 24), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), + ); + }, + ), + const Divider(height: 8), ListTile( minTileHeight: 48, leading: const Icon(Symbols.copy_all), diff --git a/lib/widgets/post/compose_recorder.dart b/lib/widgets/post/compose_recorder.dart index f64d85c5..02e167f7 100644 --- a/lib/widgets/post/compose_recorder.dart +++ b/lib/widgets/post/compose_recorder.dart @@ -1,6 +1,4 @@ import 'dart:async'; -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; @@ -9,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/services/time.dart'; +import 'package:island/talker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -66,7 +65,7 @@ class ComposeRecorder extends HookConsumerWidget { useEffect(() { return () { // Called when widget is unmounted - log('[Recorder] Clean up!'); + talker.info('[Recorder] Clean up!'); originalAmplitude?.cancel(); amplitudeStream.close(); record.dispose(); diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index 4201dbec..b301d716 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:mime/mime.dart'; import 'package:dio/dio.dart'; @@ -23,8 +25,7 @@ import 'package:island/widgets/post/compose_recorder.dart'; import 'package:island/pods/file_pool.dart'; import 'package:pasteboard/pasteboard.dart'; import 'package:textfield_tags/textfield_tags.dart'; -import 'dart:async'; -import 'dart:developer'; +import 'package:island/talker.dart'; class ComposeState { final TextEditingController titleController; @@ -203,7 +204,7 @@ class ComposeLogic { state.attachments.value = clone; } } catch (err) { - log('[ComposeLogic] Failed to upload attachment: $err'); + talker.error('[ComposeLogic] Failed to upload attachment: $err'); // Continue with other attachments even if one fails } } @@ -263,7 +264,7 @@ class ComposeLogic { await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft); } catch (e) { - log('[ComposeLogic] Failed to save draft, error: $e'); + talker.error('[ComposeLogic] Failed to save draft, error: $e'); } } @@ -336,7 +337,9 @@ class ComposeLogic { await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft); } catch (e) { - log('[ComposeLogic] Failed to save draft without upload, error: $e'); + talker.error( + '[ComposeLogic] Failed to save draft without upload, error: $e', + ); } } @@ -352,7 +355,7 @@ class ComposeLogic { showSnackBar('draftSaved'.tr()); } } catch (e) { - log('[ComposeLogic] Failed to save draft manually, error: $e'); + talker.error('[ComposeLogic] Failed to save draft manually, error: $e'); if (context.mounted) { showSnackBar('draftSaveFailed'.tr()); }