♻️ Refactor logger system

This commit is contained in:
2025-09-28 00:39:17 +08:00
parent 42bd7f97cb
commit fffca4a78c
28 changed files with 278 additions and 355 deletions

View File

@@ -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<void> _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((_) {

View File

@@ -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<void> _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<String, dynamic>) {
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<String, dynamic> 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<String, dynamic> 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');
}
},
});

View File

@@ -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<void> start() async {
try {
final ipcPath = Platform.isWindows
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) {
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,
);
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);
},
onDone: () {
talker.log('IPC connection closed');
socket.close();
}, onError: (e) {
developer.log('IPC data error: $e', name: kRpcIpcLogPrefix);
},
onError: (e) {
talker.log('IPC data error: $e');
socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString());
});
final totalDuration = DateTime.now().difference(startTime).inMicroseconds;
developer.log(
'_handleIpcData took $totalDuration microseconds',
name: kRpcIpcLogPrefix,
},
);
final totalDuration = DateTime.now().difference(startTime).inMicroseconds;
talker.log('_handleIpcData took $totalDuration microseconds');
}
Future<String> _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<String, dynamic> msg) {
developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix);
talker.log('IPC sending: $msg');
final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg);
socket.add(packet);
}

View File

@@ -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<void> 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 &&

View File

@@ -6,7 +6,7 @@ part of 'call.dart';
// RiverpodGenerator
// **************************************************************************
String _$callNotifierHash() => r'eb9bd41b97e9b5e9d54007c8327edb6567458846';
String _$callNotifierHash() => r'd374402e51d331cf40724e51fd86bce3c5504776';
/// See also [CallNotifier].
@ProviderFor(CallNotifier)

View File

@@ -7,7 +7,7 @@ part of 'chat_online_count.dart';
// **************************************************************************
String _$chatOnlineCountNotifierHash() =>
r'254ed141ffd99585d898203b3d2b86c4d18db80d';
r'19af8fd0e9f62c65e12a68215406776085235fa3';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -7,7 +7,7 @@ part of 'chat_subscribe.dart';
// **************************************************************************
String _$chatSubscribeNotifierHash() =>
r'10a6b2c687149ebb419e4c96349d8bab1f183ec6';
r'df65ecf15d0e97d7e6850ac57b4e681606e77179';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -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<LocalChatMessage> 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<void> 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<void> 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<void> 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<int, double>)? 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<void> 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<void> 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<void> 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<void> 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<void> 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<LocalChatMessage?> 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<int> 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;

View File

@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
// RiverpodGenerator
// **************************************************************************
String _$messagesNotifierHash() => r'4257c9b3792418e913d0bac3ef58e727314635af';
String _$messagesNotifierHash() => r'3aad1491b777570913f3867abd280fa59949b1f1';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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?,
));
}

View File

@@ -7,7 +7,7 @@ part of 'config.dart';
// **************************************************************************
String _$appSettingsNotifierHash() =>
r'3b0967a39a375c664c3fd44cbee7936b8b2f5fec';
r'c3042f77067b5f36102f17277e174a756121ac74';
/// See also [AppSettingsNotifier].
@ProviderFor(AppSettingsNotifier)

View File

@@ -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<Dio>((ref) {
),
);
dio.interceptors.add(
dio.interceptors.addAll([
InterceptorsWrapper(
onRequest: (
RequestOptions options,
@@ -97,7 +99,15 @@ final apiClientProvider = Provider<Dio>((ref) {
return handler.next(options);
},
),
);
TalkerDioLogger(
talker: talker,
settings: const TalkerDioLoggerSettings(
printRequestHeaders: true,
printResponseHeaders: true,
printResponseMessage: true,
),
),
]);
return dio;
});

View File

@@ -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<AsyncValue<SnAccount?>> {
final Ref _ref;
@@ -21,7 +21,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
Future<void> 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<AsyncValue<SnAccount?>> {
}
});
}
log(
talker.error(
"[UserInfo] Failed to fetch user info...",
name: 'UserInfoNotifier',
error: error,
stackTrace: stackTrace,
error,
stackTrace,
);
state = AsyncValue.data(null);
}

View File

@@ -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)));
}

View File

@@ -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<NavigatorState>();
@@ -96,6 +98,7 @@ final routerProvider = Provider<GoRouter>((ref) {
observers: [
if (_supportsAnalytics)
FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance),
TalkerRouteObserver(talker),
],
routes: [
ShellRoute(
@@ -132,6 +135,11 @@ final routerProvider = Provider<GoRouter>((ref) {
return CallScreen(roomId: id);
},
),
GoRoute(
name: 'logs',
path: '/logs',
builder: (context, state) => TalkerScreen(talker: talker),
),
GoRoute(
name: 'accountCalendar',
path: '/account/:name/calendar',

View File

@@ -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);

View File

@@ -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<PollEditorState> {
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);
}

View File

@@ -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<WebSocketPacket> 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<WebSocketPacket> 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<WebSocketPacket> 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<void> 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) {

View File

@@ -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<void> initializeLocalNotifications() async {
// Initialize Windows notification for Windows platform
windowsNotification = windows_notification.WindowsNotification(
windowsNotification = winty.WindowsNotification(
applicationId: 'dev.solsynth.solian',
);
@@ -61,7 +59,7 @@ StreamSubscription<WebSocketPacket> 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<WebSocketPacket> 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<void> 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) {

View File

@@ -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<void> 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<String, dynamic>;
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<String?> _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<bool> _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),

4
lib/talker.dart Normal file
View File

@@ -0,0 +1,4 @@
import 'package:talker_flutter/talker_flutter.dart';
final talker = TalkerFlutter.init();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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<UniversalAudio> {
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<UniversalAudio> {
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);

View File

@@ -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<UniversalVideo> {
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<UniversalVideo> {
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);

View File

@@ -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<void> _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),

View File

@@ -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();

View File

@@ -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());
}