♻️ 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 'dart:io';
import 'package:croppy/croppy.dart'; import 'package:croppy/croppy.dart';
import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:easy_localization/easy_localization.dart' hide TextDirection;
import 'package:firebase_core/firebase_core.dart'; 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:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker_android/image_picker_android.dart'; import 'package:image_picker_android/image_picker_android.dart';
import 'package:island/talker.dart';
import 'package:island/firebase_options.dart'; import 'package:island/firebase_options.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/theme.dart'; import 'package:island/pods/theme.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/route.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:shared_preferences/shared_preferences.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:flutter_native_splash/flutter_native_splash.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:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
log('Handling a background message: ${message.messageId}'); talker.info('Handling a background message: ${message.messageId}');
} }
void main() async { void main() async {
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
log( talker.info(
"[SplashScreen] Keeping the flash screen to loading other resources...", "[SplashScreen] Keeping the flash screen to loading other resources...",
); );
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
@@ -73,17 +72,17 @@ void main() async {
} }
} }
log("[SplashScreen] Firebase is ready!"); talker.info("[SplashScreen] Firebase is ready!");
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }
try { try {
log("[SplashScreen] Loading timezone database..."); talker.info("[SplashScreen] Loading timezone database...");
await initializeTzdb(); await initializeTzdb();
log("[SplashScreen] Time zone database was loaded!"); talker.info("[SplashScreen] Time zone database was loaded!");
} catch (err) { } catch (err) {
log("[SplashScreen] Failed to load timezone database... $err"); talker.error("[SplashScreen] Failed to load timezone database... $err");
} }
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@@ -106,7 +105,7 @@ void main() async {
initialSize = Size(width, height); initialSize = Size(width, height);
} }
} catch (e) { } catch (e) {
log("[SplashScreen] Failed to parse saved window size: $e"); talker.error("[SplashScreen] Failed to parse saved window size: $e");
initialSize = defaultSize; initialSize = defaultSize;
} }
} }
@@ -125,7 +124,7 @@ void main() async {
await windowManager.focus(); await windowManager.focus();
final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0; final opacity = prefs.getDouble(kAppWindowOpacity) ?? 1.0;
await windowManager.setOpacity(opacity); await windowManager.setOpacity(opacity);
log( talker.info(
"[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}", "[SplashScreen] Desktop window is ready with size: ${initialSize.width}x${initialSize.height}",
); );
}); });
@@ -137,16 +136,17 @@ void main() async {
if (imagePickerImplementation is ImagePickerAndroid) { if (imagePickerImplementation is ImagePickerAndroid) {
imagePickerImplementation.useAndroidPhotoPicker = true; imagePickerImplementation.useAndroidPhotoPicker = true;
} }
log("[SplashScreen] Android image picker is ready!"); talker.info("[SplashScreen] Android image picker is ready!");
} }
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
log("[SplashScreen] Now hiding the splash screen..."); talker.info("[SplashScreen] Now hiding the splash screen...");
} }
runApp( runApp(
ProviderScope( ProviderScope(
observers: [TalkerRiverpodObserver(talker: talker)],
overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], overrides: [sharedPreferencesProvider.overrideWithValue(prefs)],
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
@@ -227,7 +227,9 @@ class IslandApp extends HookConsumerWidget {
final onMessageSubscription = FirebaseMessaging.onMessage.listen(( final onMessageSubscription = FirebaseMessaging.onMessage.listen((
message, message,
) { ) {
log('Foreground message received: ${message.messageId}'); talker.info(
'[Notification] foreground message received: ${message.messageId}',
);
handleMessage(message); handleMessage(message);
}); });
@@ -241,7 +243,7 @@ class IslandApp extends HookConsumerWidget {
// Load userinfo // Load userinfo
final userNotifier = ref.read(userInfoProvider.notifier); final userNotifier = ref.read(userInfoProvider.notifier);
ref.listen(websocketStateProvider, (_, state) { ref.listen(websocketStateProvider, (_, state) {
log('[WebSocket] $state'); talker.info('[WebSocket] $state');
}); });
Future(() { Future(() {
userNotifier.fetchUser().then((_) { userNotifier.fetchUser().then((_) {

View File

@@ -1,11 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf/shelf_io.dart' as shelf_io;
@@ -69,13 +69,13 @@ class ActivityRpcServer {
// Start WebSocket server // Start WebSocket server
while (port <= portRange[1]) { while (port <= portRange[1]) {
developer.log('Trying port $port', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] Trying port $port');
try { try {
_httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port); _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 { shelf_io.serveRequests(_httpServer!, (Request request) async {
developer.log('New request', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] New request');
if (request.headers['upgrade']?.toLowerCase() == 'websocket') { if (request.headers['upgrade']?.toLowerCase() == 'websocket') {
final handler = webSocketHandler((WebSocketChannel channel, _) { final handler = webSocketHandler((WebSocketChannel channel, _) {
_wsSockets.add(channel); _wsSockets.add(channel);
@@ -83,19 +83,16 @@ class ActivityRpcServer {
}); });
return handler(request); return handler(request);
} }
developer.log( talker.log('New request disposed due to not websocket');
'New request disposed due to not websocket',
name: kRpcLogPrefix,
);
return Response.notFound('Not a WebSocket request'); return Response.notFound('Not a WebSocket request');
}); });
wsSuccess = true; wsSuccess = true;
break; break;
} catch (e) { } catch (e) {
if (e is SocketException && e.osError?.errorCode == 98) { if (e is SocketException && e.osError?.errorCode == 98) {
developer.log('$port in use!', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] $port in use!');
} else { } else {
developer.log('HTTP error: $e', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] HTTP error: $e');
} }
port++; port++;
await Future.delayed(Duration(milliseconds: 100)); // Add delay await Future.delayed(Duration(milliseconds: 100)); // Add delay
@@ -121,13 +118,10 @@ class ActivityRpcServer {
await _ipcServer!.start(); await _ipcServer!.start();
} catch (e) { } catch (e) {
developer.log('IPC server error: $e', name: kRpcIpcLogPrefix); talker.log('[$kRpcLogPrefix] IPC server error: $e');
} }
} else { } else {
developer.log( talker.log('IPC server disabled on macOS or web in production mode');
'IPC server disabled on macOS or web in production mode',
name: kRpcIpcLogPrefix,
);
} }
} }
@@ -138,7 +132,7 @@ class ActivityRpcServer {
try { try {
await socket.sink.close(); await socket.sink.close();
} catch (e) { } catch (e) {
developer.log('Error closing WebSocket: $e', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] Error closing WebSocket: $e');
} }
} }
_wsSockets.clear(); _wsSockets.clear();
@@ -147,7 +141,7 @@ class ActivityRpcServer {
// Stop IPC server // Stop IPC server
await _ipcServer?.stop(); await _ipcServer?.stop();
developer.log('Servers stopped', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] Servers stopped');
} }
// Handle new WebSocket connection // Handle new WebSocket connection
@@ -159,10 +153,7 @@ class ActivityRpcServer {
final clientId = params['client_id'] ?? ''; final clientId = params['client_id'] ?? '';
final origin = request.headers['origin'] ?? ''; final origin = request.headers['origin'] ?? '';
developer.log( talker.log('New WS connection! origin: $origin, params: $params');
'New WS connection! origin: $origin, params: $params',
name: kRpcLogPrefix,
);
if (origin.isNotEmpty && if (origin.isNotEmpty &&
![ ![
@@ -170,22 +161,19 @@ class ActivityRpcServer {
'https://ptb.discord.com', 'https://ptb.discord.com',
'https://canary.discord.com', 'https://canary.discord.com',
].contains(origin)) { ].contains(origin)) {
developer.log('Disallowed origin: $origin', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] Disallowed origin: $origin');
socket.sink.close(); socket.sink.close();
return; return;
} }
if (encoding != 'json') { if (encoding != 'json') {
developer.log( talker.log('Unsupported encoding requested: $encoding');
'Unsupported encoding requested: $encoding',
name: kRpcLogPrefix,
);
socket.sink.close(); socket.sink.close();
return; return;
} }
if (ver != 1) { if (ver != 1) {
developer.log('Unsupported version requested: $ver', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] Unsupported version requested: $ver');
socket.sink.close(); socket.sink.close();
return; return;
} }
@@ -195,10 +183,10 @@ class ActivityRpcServer {
socket.stream.listen( socket.stream.listen(
(data) => _onWsMessage(socketWithMeta, data), (data) => _onWsMessage(socketWithMeta, data),
onError: (e) { onError: (e) {
developer.log('WS socket error: $e', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] WS socket error: $e');
}, },
onDone: () { onDone: () {
developer.log('WS socket closed', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] WS socket closed');
handlers['close']?.call(socketWithMeta); handlers['close']?.call(socketWithMeta);
_wsSockets.remove(socket); _wsSockets.remove(socket);
}, },
@@ -210,25 +198,19 @@ class ActivityRpcServer {
// Handle incoming WebSocket message // Handle incoming WebSocket message
Future<void> _onWsMessage(_WsSocketWrapper socket, dynamic data) async { Future<void> _onWsMessage(_WsSocketWrapper socket, dynamic data) async {
if (data is! String) { if (data is! String) {
developer.log( talker.log('Invalid WebSocket message: not a string');
'Invalid WebSocket message: not a string',
name: kRpcLogPrefix,
);
return; return;
} }
try { try {
final jsonData = await compute(jsonDecode, data); final jsonData = await compute(jsonDecode, data);
if (jsonData is! Map<String, dynamic>) { if (jsonData is! Map<String, dynamic>) {
developer.log( talker.log('Invalid WebSocket message: not a JSON object');
'Invalid WebSocket message: not a JSON object',
name: kRpcLogPrefix,
);
return; return;
} }
developer.log('WS message: $jsonData', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] WS message: $jsonData');
handlers['message']?.call(socket, jsonData); handlers['message']?.call(socket, jsonData);
} catch (e) { } 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) { void _handleIpcPacket(IpcSocketWrapper socket, IpcPacket packet) {
switch (packet.type) { switch (packet.type) {
case IpcTypes.ping: case IpcTypes.ping:
developer.log('IPC ping received', name: kRpcIpcLogPrefix); talker.log('[$kRpcLogPrefix] IPC ping received');
socket.sendPong(packet.data); socket.sendPong(packet.data);
break; break;
case IpcTypes.pong: case IpcTypes.pong:
developer.log('IPC pong received', name: kRpcIpcLogPrefix); talker.log('[$kRpcLogPrefix] IPC pong received');
break; break;
case IpcTypes.handshake: case IpcTypes.handshake:
@@ -256,7 +238,7 @@ class ActivityRpcServer {
if (!socket.handshook) { if (!socket.handshook) {
throw Exception('Need to handshake first'); 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); handlers['message']?.call(socket, packet.data);
break; break;
@@ -271,22 +253,19 @@ class ActivityRpcServer {
// Handle IPC handshake // Handle IPC handshake
void _onIpcHandshake(IpcSocketWrapper socket, Map<String, dynamic> params) { 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 ver = int.tryParse(params['v']?.toString() ?? '1') ?? 1;
final clientId = params['client_id']?.toString() ?? ''; final clientId = params['client_id']?.toString() ?? '';
if (ver != 1) { if (ver != 1) {
developer.log( talker.log('IPC unsupported version requested: $ver');
'IPC unsupported version requested: $ver',
name: kRpcIpcLogPrefix,
);
socket.closeWithCode(IpcErrorCodes.invalidVersion); socket.closeWithCode(IpcErrorCodes.invalidVersion);
return; return;
} }
if (clientId.isEmpty) { if (clientId.isEmpty) {
developer.log('IPC client ID required', name: kRpcIpcLogPrefix); talker.log('[$kRpcLogPrefix] IPC client ID required');
socket.closeWithCode(IpcErrorCodes.invalidClientId); socket.closeWithCode(IpcErrorCodes.invalidClientId);
return; return;
} }
@@ -306,7 +285,7 @@ class _WsSocketWrapper {
_WsSocketWrapper(this.channel, this.clientId, this.encoding); _WsSocketWrapper(this.channel, this.clientId, this.encoding);
void send(Map<String, dynamic> msg) { void send(Map<String, dynamic> msg) {
developer.log('WS sending: $msg', name: kRpcLogPrefix); talker.log('[$kRpcLogPrefix] WS sending: $msg');
channel.sink.add(jsonEncode(msg)); channel.sink.add(jsonEncode(msg));
} }
} }
@@ -398,12 +377,7 @@ final rpcServerStateProvider =
final appId = socket.clientId; final appId = socket.clientId;
final meta = data['args']['activity']; final meta = data['args']['activity'];
try { try {
await setRemoteActivityStatus( await setRemoteActivityStatus(ref, label, appId, meta);
ref,
label,
appId,
meta,
);
final now = DateTime.now(); final now = DateTime.now();
final status = SnAccountStatus( final status = SnAccountStatus(
id: 'local_$appId', id: 'local_$appId',
@@ -422,10 +396,7 @@ final rpcServerStateProvider =
); );
ref.read(currentAccountStatusProvider.notifier).setStatus(status); ref.read(currentAccountStatusProvider.notifier).setStatus(status);
} catch (e) { } catch (e) {
developer.log( talker.log('Failed to set remote activity status: $e');
'Failed to set remote activity status: $e',
name: kRpcLogPrefix,
);
} }
socket.send({ socket.send({
'cmd': 'SET_ACTIVITY', 'cmd': 'SET_ACTIVITY',
@@ -442,10 +413,7 @@ final rpcServerStateProvider =
await unsetRemoteActivityStatus(ref, appId); await unsetRemoteActivityStatus(ref, appId);
ref.read(currentAccountStatusProvider.notifier).clearStatus(); ref.read(currentAccountStatusProvider.notifier).clearStatus();
} catch (e) { } catch (e) {
developer.log( talker.log('Failed to unset remote activity status: $e');
'Failed to unset remote activity status: $e',
name: kRpcLogPrefix,
);
} }
}, },
}); });

View File

@@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:dart_ipc/dart_ipc.dart'; import 'package:dart_ipc/dart_ipc.dart';
import 'package:island/talker.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
const String kRpcIpcLogPrefix = 'arRPC.ipc'; const String kRpcIpcLogPrefix = 'arRPC.ipc';
@@ -128,23 +128,18 @@ class MultiPlatformIpcServer extends IpcServer {
@override @override
Future<void> start() async { Future<void> start() async {
try { try {
final ipcPath = Platform.isWindows final ipcPath =
Platform.isWindows
? r'\\.\pipe\discord-ipc-0' ? r'\\.\pipe\discord-ipc-0'
: await _findAvailableUnixIpcPath(); : await _findAvailableUnixIpcPath();
final serverSocket = await bind(ipcPath); final serverSocket = await bind(ipcPath);
developer.log( talker.log('IPC listening at $ipcPath');
'IPC listening at $ipcPath',
name: kRpcIpcLogPrefix,
);
_serverSubscription = serverSocket.listen((socket) { _serverSubscription = serverSocket.listen((socket) {
final socketWrapper = MultiPlatformIpcSocketWrapper(socket); final socketWrapper = MultiPlatformIpcSocketWrapper(socket);
addSocket(socketWrapper); addSocket(socketWrapper);
developer.log( talker.log('New IPC connection!');
'New IPC connection!',
name: kRpcIpcLogPrefix,
);
_handleIpcData(socketWrapper); _handleIpcData(socketWrapper);
}); });
} catch (e) { } catch (e) {
@@ -158,7 +153,7 @@ class MultiPlatformIpcServer extends IpcServer {
try { try {
socket.close(); socket.close();
} catch (e) { } catch (e) {
developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix); talker.log('Error closing IPC socket: $e');
} }
} }
sockets.clear(); sockets.clear();
@@ -168,31 +163,30 @@ class MultiPlatformIpcServer extends IpcServer {
// Handle incoming IPC data // Handle incoming IPC data
void _handleIpcData(MultiPlatformIpcSocketWrapper socket) { void _handleIpcData(MultiPlatformIpcSocketWrapper socket) {
final startTime = DateTime.now(); final startTime = DateTime.now();
socket.socket.listen((data) { socket.socket.listen(
(data) {
final readStart = DateTime.now(); final readStart = DateTime.now();
socket.addData(data); socket.addData(data);
final readDuration = DateTime.now().difference(readStart).inMicroseconds; final readDuration =
developer.log( DateTime.now().difference(readStart).inMicroseconds;
'Read data took $readDuration microseconds', talker.log('Read data took $readDuration microseconds');
name: kRpcIpcLogPrefix,
);
final packets = socket.readPackets(); final packets = socket.readPackets();
for (final packet in packets) { for (final packet in packets) {
handlePacket?.call(socket, packet, {}); handlePacket?.call(socket, packet, {});
} }
}, onDone: () { },
developer.log('IPC connection closed', name: kRpcIpcLogPrefix); onDone: () {
talker.log('IPC connection closed');
socket.close(); 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()); 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 { Future<String> _getMacOsSystemTmpDir() async {
@@ -212,10 +206,7 @@ class MultiPlatformIpcServer extends IpcServer {
baseDirs.add(macTempDir); baseDirs.add(macTempDir);
} }
} catch (e) { } catch (e) {
developer.log( talker.log('Failed to get macOS system temp dir: $e');
'Failed to get macOS system temp dir: $e',
name: kRpcIpcLogPrefix,
);
} }
} }
@@ -241,17 +232,11 @@ class MultiPlatformIpcServer extends IpcServer {
try { try {
await File(socketPath).delete(); await File(socketPath).delete();
} catch (_) {} } catch (_) {}
developer.log( talker.log('IPC socket will be created at: $socketPath');
'IPC socket will be created at: $socketPath',
name: kRpcIpcLogPrefix,
);
return socketPath; return socketPath;
} catch (e) { } catch (e) {
if (i == 0) { if (i == 0) {
developer.log( talker.log('IPC path $socketPath not available: $e');
'IPC path $socketPath not available: $e',
name: kRpcIpcLogPrefix,
);
} }
continue; continue;
} }
@@ -271,7 +256,7 @@ class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper {
@override @override
void send(Map<String, dynamic> msg) { 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); final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg);
socket.add(packet); socket.add(packet);
} }

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.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/pods/network.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:island/talker.dart';
part 'call.g.dart'; part 'call.g.dart';
part 'call.freezed.dart'; part 'call.freezed.dart';
@@ -212,7 +212,7 @@ class CallNotifier extends _$CallNotifier {
Future<void> joinRoom(String roomId) async { Future<void> joinRoom(String roomId) async {
if (_roomId == roomId && _room != null) { if (_roomId == roomId && _room != null) {
log('[Call] Call skipped. Already has data'); talker.info('[Call] Call skipped. Already has data');
return; return;
} else if (_room != null) { } else if (_room != null) {
if (!_room!.isDisposed && if (!_room!.isDisposed &&

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import "dart:async"; import "dart:async";
import "dart:developer" as developer;
import "package:dio/dio.dart"; import "package:dio/dio.dart";
import "package:drift/drift.dart" show Variable; import "package:drift/drift.dart" show Variable;
import "package:easy_localization/easy_localization.dart"; 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/lifecycle.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
import "package:island/services/file.dart"; import "package:island/services/file.dart";
import "package:island/talker.dart";
import "package:island/widgets/alert.dart"; import "package:island/widgets/alert.dart";
import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:riverpod_annotation/riverpod_annotation.dart";
import "package:uuid/uuid.dart"; import "package:uuid/uuid.dart";
@@ -60,10 +60,7 @@ class MessagesNotifier extends _$MessagesNotifier {
_identity = identity; _identity = identity;
} }
developer.log( talker.log('MessagesNotifier built for room $roomId');
'MessagesNotifier built for room $roomId',
name: 'MessagesNotifier',
);
// Only setup sync and lifecycle listeners if user is a member // Only setup sync and lifecycle listeners if user is a member
if (identity != null) { if (identity != null) {
@@ -71,24 +68,15 @@ class MessagesNotifier extends _$MessagesNotifier {
next.whenData((state) { next.whenData((state) {
if (state == AppLifecycleState.paused) { if (state == AppLifecycleState.paused) {
_lastPauseTime = DateTime.now(); _lastPauseTime = DateTime.now();
developer.log( talker.log('App paused, recording time');
'App paused, recording time',
name: 'MessagesNotifier',
);
} else if (state == AppLifecycleState.resumed) { } else if (state == AppLifecycleState.resumed) {
if (_lastPauseTime != null) { if (_lastPauseTime != null) {
final diff = DateTime.now().difference(_lastPauseTime!); final diff = DateTime.now().difference(_lastPauseTime!);
if (diff > const Duration(minutes: 1)) { if (diff > const Duration(minutes: 1)) {
developer.log( talker.log('App resumed after >1 min, syncing messages');
'App resumed after >1 min, syncing messages',
name: 'MessagesNotifier',
);
syncMessages(); syncMessages();
} else { } else {
developer.log( talker.log('App resumed within 1 min, skipping sync');
'App resumed within 1 min, skipping sync',
name: 'MessagesNotifier',
);
} }
} }
} }
@@ -109,10 +97,7 @@ class MessagesNotifier extends _$MessagesNotifier {
int offset = 0, int offset = 0,
int take = 20, int take = 20,
}) async { }) async {
developer.log( talker.log('Getting cached messages from offset $offset, take $take');
'Getting cached messages from offset $offset, take $take',
name: 'MessagesNotifier',
);
final List<LocalChatMessage> dbMessages; final List<LocalChatMessage> dbMessages;
if (_searchQuery != null && _searchQuery!.isNotEmpty) { if (_searchQuery != null && _searchQuery!.isNotEmpty) {
dbMessages = await _database.searchMessages( dbMessages = await _database.searchMessages(
@@ -174,10 +159,7 @@ class MessagesNotifier extends _$MessagesNotifier {
int offset = 0, int offset = 0,
int take = 20, int take = 20,
}) async { }) async {
developer.log( talker.log('Fetching messages from API, offset $offset, take $take');
'Fetching messages from API, offset $offset, take $take',
name: 'MessagesNotifier',
);
if (_totalCount == null) { if (_totalCount == null) {
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages', '/sphere/chat/$_roomId/messages',
@@ -221,15 +203,12 @@ class MessagesNotifier extends _$MessagesNotifier {
Future<void> syncMessages() async { Future<void> syncMessages() async {
if (_isSyncing) { if (_isSyncing) {
developer.log( talker.log('Sync already in progress, skipping.');
'Sync already in progress, skipping.',
name: 'MessagesNotifier',
);
return; return;
} }
_isSyncing = true; _isSyncing = true;
developer.log('Starting message sync', name: 'MessagesNotifier'); talker.log('Starting message sync');
Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true); Future.microtask(() => ref.read(isSyncingProvider.notifier).state = true);
try { try {
final dbMessages = await _database.getMessagesForRoom( final dbMessages = await _database.getMessagesForRoom(
@@ -243,10 +222,7 @@ class MessagesNotifier extends _$MessagesNotifier {
: _database.companionToMessage(dbMessages.first); : _database.companionToMessage(dbMessages.first);
if (lastMessage == null) { if (lastMessage == null) {
developer.log( talker.log('No local messages, fetching from network');
'No local messages, fetching from network',
name: 'MessagesNotifier',
);
final newMessages = await _fetchAndCacheMessages( final newMessages = await _fetchAndCacheMessages(
offset: 0, offset: 0,
take: _pageSize, take: _pageSize,
@@ -264,10 +240,7 @@ class MessagesNotifier extends _$MessagesNotifier {
); );
final response = MessageSyncResponse.fromJson(resp.data); final response = MessageSyncResponse.fromJson(resp.data);
developer.log( talker.log('Sync response: ${response.messages.length} changes');
'Sync response: ${response.messages.length} changes',
name: 'MessagesNotifier',
);
for (final message in response.messages) { for (final message in response.messages) {
switch (message.type) { switch (message.type) {
case "messages.update": case "messages.update":
@@ -282,15 +255,14 @@ class MessagesNotifier extends _$MessagesNotifier {
await receiveMessage(message); await receiveMessage(message);
} }
} catch (err, stackTrace) { } catch (err, stackTrace) {
developer.log( talker.log(
'Error syncing messages', 'Error syncing messages',
name: 'MessagesNotifier', exception: err,
error: err,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {
developer.log('Finished message sync', name: 'MessagesNotifier'); talker.log('Finished message sync');
Future.microtask( Future.microtask(
() => ref.read(isSyncingProvider.notifier).state = false, () => ref.read(isSyncingProvider.notifier).state = false,
); );
@@ -340,7 +312,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> loadInitial() async { Future<void> loadInitial() async {
developer.log('Loading initial messages', name: 'MessagesNotifier'); talker.log('Loading initial messages');
if (_searchQuery == null || _searchQuery!.isEmpty) { if (_searchQuery == null || _searchQuery!.isEmpty) {
syncMessages(); syncMessages();
} }
@@ -354,7 +326,7 @@ class MessagesNotifier extends _$MessagesNotifier {
Future<void> loadMore() async { Future<void> loadMore() async {
if (!_hasMore || state is AsyncLoading) return; if (!_hasMore || state is AsyncLoading) return;
developer.log('Loading more messages', name: 'MessagesNotifier'); talker.log('Loading more messages');
try { try {
final currentMessages = state.value ?? []; final currentMessages = state.value ?? [];
@@ -370,10 +342,10 @@ class MessagesNotifier extends _$MessagesNotifier {
_sortMessages([...currentMessages, ...newMessages]), _sortMessages([...currentMessages, ...newMessages]),
); );
} catch (err, stackTrace) { } catch (err, stackTrace) {
developer.log( talker.log(
'Error loading more messages', 'Error loading more messages',
name: 'MessagesNotifier',
error: err, exception: err,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
showErrorAlert(err); showErrorAlert(err);
@@ -389,10 +361,7 @@ class MessagesNotifier extends _$MessagesNotifier {
Function(String, Map<int, double>)? onProgress, Function(String, Map<int, double>)? onProgress,
}) async { }) async {
final nonce = const Uuid().v4(); final nonce = const Uuid().v4();
developer.log( talker.log('Sending message with nonce $nonce');
'Sending message with nonce $nonce',
name: 'MessagesNotifier',
);
final baseUrl = ref.read(serverUrlProvider); final baseUrl = ref.read(serverUrlProvider);
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Access token is null'); if (token == null) throw ArgumentError('Access token is null');
@@ -496,15 +465,12 @@ class MessagesNotifier extends _$MessagesNotifier {
}).toList(); }).toList();
state = AsyncValue.data(newMessages); state = AsyncValue.data(newMessages);
} }
developer.log( talker.log('Message with nonce $nonce sent successfully');
'Message with nonce $nonce sent successfully',
name: 'MessagesNotifier',
);
} catch (e, stackTrace) { } catch (e, stackTrace) {
developer.log( talker.log(
'Failed to send message with nonce $nonce', 'Failed to send message with nonce $nonce',
name: 'MessagesNotifier',
error: e, exception: e,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
localMessage.status = MessageStatus.failed; localMessage.status = MessageStatus.failed;
@@ -526,10 +492,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> retryMessage(String pendingMessageId) async { Future<void> retryMessage(String pendingMessageId) async {
developer.log( talker.log('Retrying message $pendingMessageId');
'Retrying message $pendingMessageId',
name: 'MessagesNotifier',
);
final message = await fetchMessageById(pendingMessageId); final message = await fetchMessageById(pendingMessageId);
if (message == null) { if (message == null) {
throw Exception('Message not found'); throw Exception('Message not found');
@@ -573,10 +536,10 @@ class MessagesNotifier extends _$MessagesNotifier {
}).toList(); }).toList();
state = AsyncValue.data(newMessages); state = AsyncValue.data(newMessages);
} catch (e, stackTrace) { } catch (e, stackTrace) {
developer.log( talker.log(
'Failed to retry message $pendingMessageId', 'Failed to retry message $pendingMessageId',
name: 'MessagesNotifier',
error: e, exception: e,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
message.status = MessageStatus.failed; message.status = MessageStatus.failed;
@@ -599,10 +562,7 @@ class MessagesNotifier extends _$MessagesNotifier {
Future<void> receiveMessage(SnChatMessage remoteMessage) async { Future<void> receiveMessage(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != _roomId) return;
developer.log( talker.log('Received new message ${remoteMessage.id}');
'Received new message ${remoteMessage.id}',
name: 'MessagesNotifier',
);
final localMessage = LocalChatMessage.fromRemoteMessage( final localMessage = LocalChatMessage.fromRemoteMessage(
remoteMessage, remoteMessage,
@@ -647,10 +607,7 @@ class MessagesNotifier extends _$MessagesNotifier {
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async { Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != _roomId) return;
developer.log( talker.log('Received message update ${remoteMessage.id}');
'Received message update ${remoteMessage.id}',
name: 'MessagesNotifier',
);
final targetId = remoteMessage.meta['message_id'] ?? remoteMessage.id; final targetId = remoteMessage.meta['message_id'] ?? remoteMessage.id;
final updatedMessage = LocalChatMessage.fromRemoteMessage( final updatedMessage = LocalChatMessage.fromRemoteMessage(
@@ -673,10 +630,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> receiveMessageDeletion(String messageId) async { Future<void> receiveMessageDeletion(String messageId) async {
developer.log( talker.log('Received message deletion $messageId');
'Received message deletion $messageId',
name: 'MessagesNotifier',
);
_pendingMessages.remove(messageId); _pendingMessages.remove(messageId);
final currentMessages = state.value ?? []; final currentMessages = state.value ?? [];
@@ -713,15 +667,15 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> deleteMessage(String messageId) async { Future<void> deleteMessage(String messageId) async {
developer.log('Deleting message $messageId', name: 'MessagesNotifier'); talker.log('Deleting message $messageId');
try { try {
await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId'); await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId');
await receiveMessageDeletion(messageId); await receiveMessageDeletion(messageId);
} catch (err, stackTrace) { } catch (err, stackTrace) {
developer.log( talker.log(
'Error deleting message $messageId', 'Error deleting message $messageId',
name: 'MessagesNotifier',
error: err, exception: err,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
showErrorAlert(err); showErrorAlert(err);
@@ -743,10 +697,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<LocalChatMessage?> fetchMessageById(String messageId) async { Future<LocalChatMessage?> fetchMessageById(String messageId) async {
developer.log( talker.log('Fetching message by id $messageId');
'Fetching message by id $messageId',
name: 'MessagesNotifier',
);
try { try {
final localMessage = final localMessage =
await (_database.select(_database.chatMessages) await (_database.select(_database.chatMessages)
@@ -773,24 +724,18 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<int> jumpToMessage(String messageId) async { Future<int> jumpToMessage(String messageId) async {
developer.log( talker.log('Starting jump to message $messageId');
'Starting jump to message $messageId',
name: 'MessagesNotifier',
);
if (_isJumping) { if (_isJumping) {
developer.log( talker.log('Jump already in progress, skipping');
'Jump already in progress, skipping',
name: 'MessagesNotifier',
);
return -1; return -1;
} }
_isJumping = true; _isJumping = true;
try { try {
developer.log('Fetching message $messageId', name: 'MessagesNotifier'); talker.log('Fetching message $messageId');
final message = await fetchMessageById(messageId); final message = await fetchMessageById(messageId);
if (message == null) { if (message == null) {
developer.log('Message $messageId not found', name: 'MessagesNotifier'); talker.log('Message $messageId not found');
showSnackBar('messageNotFound'.tr()); showSnackBar('messageNotFound'.tr());
return -1; return -1;
} }
@@ -801,16 +746,14 @@ class MessagesNotifier extends _$MessagesNotifier {
(m) => m.id == messageId, (m) => m.id == messageId,
); );
if (existingIndex >= 0) { if (existingIndex >= 0) {
developer.log( talker.log(
'Message $messageId already in current state at index $existingIndex, jumping directly', 'Message $messageId already in current state at index $existingIndex, jumping directly',
name: 'MessagesNotifier',
); );
return existingIndex; return existingIndex;
} }
developer.log( talker.log(
'Message $messageId not in current state, loading messages around it', 'Message $messageId not in current state, loading messages around it',
name: 'MessagesNotifier',
); );
// Count messages newer than this one // Count messages newer than this one
@@ -828,10 +771,7 @@ class MessagesNotifier extends _$MessagesNotifier {
// Load messages around this position // Load messages around this position
final offset = final offset =
(newerCount - _pageSize ~/ 2).clamp(0, double.infinity).toInt(); (newerCount - _pageSize ~/ 2).clamp(0, double.infinity).toInt();
developer.log( talker.log('Loading messages with offset $offset, take $_pageSize');
'Loading messages with offset $offset, take $_pageSize',
name: 'MessagesNotifier',
);
final loadedMessages = await _getCachedMessages( final loadedMessages = await _getCachedMessages(
offset: offset, offset: offset,
take: _pageSize, take: _pageSize,
@@ -841,9 +781,8 @@ class MessagesNotifier extends _$MessagesNotifier {
final currentIds = currentMessages.map((m) => m.id).toSet(); final currentIds = currentMessages.map((m) => m.id).toSet();
final newMessages = final newMessages =
loadedMessages.where((m) => !currentIds.contains(m.id)).toList(); loadedMessages.where((m) => !currentIds.contains(m.id)).toList();
developer.log( talker.log(
'Loaded ${loadedMessages.length} messages, ${newMessages.length} are new', 'Loaded ${loadedMessages.length} messages, ${newMessages.length} are new',
name: 'MessagesNotifier',
); );
if (newMessages.isNotEmpty) { if (newMessages.isNotEmpty) {
@@ -858,19 +797,15 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
_sortMessages(uniqueMessages); _sortMessages(uniqueMessages);
state = AsyncValue.data(uniqueMessages); state = AsyncValue.data(uniqueMessages);
developer.log( talker.log(
'Updated state with ${uniqueMessages.length} total messages', 'Updated state with ${uniqueMessages.length} total messages',
name: 'MessagesNotifier',
); );
} }
final finalIndex = (state.value ?? []).indexWhere( final finalIndex = (state.value ?? []).indexWhere(
(m) => m.id == messageId, (m) => m.id == messageId,
); );
developer.log( talker.log('Final index for message $messageId is $finalIndex');
'Final index for message $messageId is $finalIndex',
name: 'MessagesNotifier',
);
return finalIndex; return finalIndex;
} finally { } finally {
_isJumping = false; _isJumping = false;

View File

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

View File

@@ -63,7 +63,9 @@ class _$AppSettingsCopyWithImpl<$Res>
final AppSettings _self; final AppSettings _self;
final $Res Function(AppSettings) _then; 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( return _then(_self.copyWith(
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable 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 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 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 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?,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) { switch (_that) {
case _AppSettings() when $default != null: 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(); 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) { switch (_that) {
case _AppSettings(): 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` /// 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) { switch (_that) {
case _AppSettings() when $default != null: 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; return null;
} }
@@ -244,16 +247,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_
@override @override
bool operator ==(Object other) { 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 @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 @override
String toString() { 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 /// Create a copy of AppSettings
/// with the given fields replaced by the non-null parameter values. /// 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( return _then(_AppSettings(
autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable 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 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 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 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?,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,themeMode: freezed == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable
as String, as String?,
)); ));
} }

View File

@@ -7,7 +7,7 @@ part of 'config.dart';
// ************************************************************************** // **************************************************************************
String _$appSettingsNotifierHash() => String _$appSettingsNotifierHash() =>
r'3b0967a39a375c664c3fd44cbee7936b8b2f5fec'; r'c3042f77067b5f36102f17277e174a756121ac74';
/// See also [AppSettingsNotifier]. /// See also [AppSettingsNotifier].
@ProviderFor(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:device_info_plus/device_info_plus.dart';
import 'package:island/models/auth.dart'; import 'package:island/models/auth.dart';
import 'package:shared_preferences/shared_preferences.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'; import 'config.dart';
@@ -75,7 +77,7 @@ final apiClientProvider = Provider<Dio>((ref) {
), ),
); );
dio.interceptors.add( dio.interceptors.addAll([
InterceptorsWrapper( InterceptorsWrapper(
onRequest: ( onRequest: (
RequestOptions options, RequestOptions options,
@@ -97,7 +99,15 @@ final apiClientProvider = Provider<Dio>((ref) {
return handler.next(options); return handler.next(options);
}, },
), ),
); TalkerDioLogger(
talker: talker,
settings: const TalkerDioLoggerSettings(
printRequestHeaders: true,
printResponseHeaders: true,
printResponseMessage: true,
),
),
]);
return dio; return dio;
}); });

View File

@@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:dio/dio.dart'; 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/models/account.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
final Ref _ref; final Ref _ref;
@@ -21,7 +21,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
Future<void> fetchUser() async { Future<void> fetchUser() async {
final token = _ref.watch(tokenProvider); final token = _ref.watch(tokenProvider);
if (token == null) { if (token == null) {
log('[UserInfo] No token found, not going to fetch...'); talker.info('[UserInfo] No token found, not going to fetch...');
return; return;
} }
try { try {
@@ -75,11 +75,10 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
} }
}); });
} }
log( talker.error(
"[UserInfo] Failed to fetch user info...", "[UserInfo] Failed to fetch user info...",
name: 'UserInfoNotifier', error,
error: error, stackTrace,
stackTrace: stackTrace,
); );
state = AsyncValue.data(null); state = AsyncValue.data(null);
} }

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.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:island/pods/network.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:island/talker.dart';
part 'websocket.freezed.dart'; part 'websocket.freezed.dart';
part 'websocket.g.dart'; part 'websocket.g.dart';
@@ -64,7 +64,7 @@ class WebSocketService {
final url = '$baseUrl/ws'.replaceFirst('http', 'ws'); final url = '$baseUrl/ws'.replaceFirst('http', 'ws');
log('[WebSocket] Trying connecting to $url'); talker.info('[WebSocket] Trying connecting to $url');
try { try {
if (kIsWeb) { if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token')); _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token'));
@@ -88,24 +88,24 @@ class WebSocketService {
return; return;
} }
_streamController.sink.add(packet); _streamController.sink.add(packet);
log( talker.info(
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}", "[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
); );
if (packet.type == 'pong' && _heartbeatAt != null) { if (packet.type == 'pong' && _heartbeatAt != null) {
var now = DateTime.now(); var now = DateTime.now();
heartbeatDelay = now.difference(_heartbeatAt!); heartbeatDelay = now.difference(_heartbeatAt!);
log( talker.info(
"[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms", "[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms",
); );
} }
}, },
onDone: () { onDone: () {
log('[WebSocket] Connection closed, attempting to reconnect...'); talker.info('[WebSocket] Connection closed, attempting to reconnect...');
_scheduleReconnect(); _scheduleReconnect();
_statusStreamController.sink.add(WebSocketState.disconnected()); _statusStreamController.sink.add(WebSocketState.disconnected());
}, },
onError: (error) { onError: (error) {
log('[WebSocket] Error occurred: $error, attempting to reconnect...'); talker.error('[WebSocket] Error occurred: $error, attempting to reconnect...');
_scheduleReconnect(); _scheduleReconnect();
_statusStreamController.sink.add( _statusStreamController.sink.add(
WebSocketState.error(error.toString()), WebSocketState.error(error.toString()),
@@ -113,7 +113,7 @@ class WebSocketService {
}, },
); );
} catch (err) { } catch (err) {
log('[WebSocket] Failed to connect: $err'); talker.error('[WebSocket] Failed to connect: $err');
_scheduleReconnect(); _scheduleReconnect();
} }
} }
@@ -135,7 +135,7 @@ class WebSocketService {
void _beatTheHeart() { void _beatTheHeart() {
_heartbeatAt = DateTime.now(); _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))); 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/discovery/realms.dart';
import 'package:island/screens/reports/report_detail.dart'; import 'package:island/screens/reports/report_detail.dart';
import 'package:island/screens/reports/report_list.dart'; import 'package:island/screens/reports/report_list.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/post/post_shuffle.dart'; import 'package:island/widgets/post/post_shuffle.dart';
import 'package:talker_flutter/talker_flutter.dart';
// Shell route keys for nested navigation // Shell route keys for nested navigation
final rootNavigatorKey = GlobalKey<NavigatorState>(); final rootNavigatorKey = GlobalKey<NavigatorState>();
@@ -96,6 +98,7 @@ final routerProvider = Provider<GoRouter>((ref) {
observers: [ observers: [
if (_supportsAnalytics) if (_supportsAnalytics)
FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance), FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance),
TalkerRouteObserver(talker),
], ],
routes: [ routes: [
ShellRoute( ShellRoute(
@@ -132,6 +135,11 @@ final routerProvider = Provider<GoRouter>((ref) {
return CallScreen(roomId: id); return CallScreen(roomId: id);
}, },
), ),
GoRoute(
name: 'logs',
path: '/logs',
builder: (context, state) => TalkerScreen(talker: talker),
),
GoRoute( GoRoute(
name: 'accountCalendar', name: 'accountCalendar',
path: '/account/:name/calendar', path: '/account/:name/calendar',

View File

@@ -1,11 +1,10 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart' hide ConnectionState; import 'package:flutter/material.dart' hide ConnectionState;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/chat/call.dart'; import 'package:island/pods/chat/call.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/call_button.dart'; import 'package:island/widgets/chat/call_button.dart';
import 'package:island/widgets/chat/call_overlay.dart'; import 'package:island/widgets/chat/call_overlay.dart';
@@ -26,14 +25,14 @@ class CallScreen extends HookConsumerWidget {
final callNotifier = ref.watch(callNotifierProvider.notifier); final callNotifier = ref.watch(callNotifierProvider.notifier);
useEffect(() { useEffect(() {
log('[Call] Joining the call...'); talker.info('[Call] Joining the call...');
callNotifier.joinRoom(roomId).catchError((_) { callNotifier.joinRoom(roomId).catchError((_) {
showConfirmAlert( showConfirmAlert(
'Seems there already has a call connected, do you want override it?', 'Seems there already has a call connected, do you want override it?',
'Call already connected', 'Call already connected',
).then((value) { ).then((value) {
if (value != true) return; if (value != true) return;
log('[Call] Joining the call... with overrides'); talker.info('[Call] Joining the call... with overrides');
callNotifier.disconnect(); callNotifier.disconnect();
callNotifier.dispose(); callNotifier.dispose();
callNotifier.joinRoom(roomId); callNotifier.joinRoom(roomId);

View File

@@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -7,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/models/poll.dart'; import 'package:island/models/poll.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
@@ -92,10 +91,10 @@ class PollEditor extends Notifier<PollEditorState> {
questions: poll.questions, questions: poll.questions,
); );
} on DioException catch (e) { } 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. // Keep state with id set; UI may handle error display.
} catch (e) { } catch (e) {
log('Unexpected error loading poll $id: $e'); talker.error('Unexpected error loading poll $id: $e');
} finally { } finally {
if (context.mounted) hideLoadingModal(context); if (context.mounted) hideLoadingModal(context);
} }

View File

@@ -1,7 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -13,6 +11,7 @@ import 'package:island/main.dart';
import 'package:island/route.dart'; import 'package:island/route.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/app_notification.dart'; import 'package:island/widgets/app_notification.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
@@ -96,7 +95,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
final notification = SnNotification.fromJson(pkt.data!); final notification = SnNotification.fromJson(pkt.data!);
if (_appLifecycleState == AppLifecycleState.resumed) { if (_appLifecycleState == AppLifecycleState.resumed) {
// App is focused, show in-app notification // App is focused, show in-app notification
log( talker.info(
'[Notification] Showing in-app notification: ${notification.title}', '[Notification] Showing in-app notification: ${notification.title}',
); );
showTopSnackBar( showTopSnackBar(
@@ -142,7 +141,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
} else { } else {
// App is in background, show system notification (only on supported platforms) // App is in background, show system notification (only on supported platforms)
if (!kIsWeb && !Platform.isIOS) { if (!kIsWeb && !Platform.isIOS) {
log( talker.info(
'[Notification] Showing system notification: ${notification.title}', '[Notification] Showing system notification: ${notification.title}',
); );
@@ -167,7 +166,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
payload: notification.meta['action_uri'] as String?, payload: notification.meta['action_uri'] as String?,
); );
} else { } else {
log( talker.info(
'[Notification] Skipping system notification for unsupported platform: ${notification.title}', '[Notification] Skipping system notification for unsupported platform: ${notification.title}',
); );
} }
@@ -206,7 +205,7 @@ Future<void> subscribePushNotification(
_putTokenToRemote(apiClient, fcmToken, 1); _putTokenToRemote(apiClient, fcmToken, 1);
}) })
.onError((err) { .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) { if (deviceToken != null) {

View File

@@ -1,7 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -11,17 +9,17 @@ import 'package:island/main.dart';
import 'package:island/route.dart'; import 'package:island/route.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/app_notification.dart'; import 'package:island/widgets/app_notification.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:windows_notification/windows_notification.dart' import 'package:windows_notification/windows_notification.dart' as winty;
as windows_notification;
import 'package:windows_notification/notification_message.dart'; import 'package:windows_notification/notification_message.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
// Windows notification instance // Windows notification instance
windows_notification.WindowsNotification? windowsNotification; winty.WindowsNotification? windowsNotification;
AppLifecycleState _appLifecycleState = AppLifecycleState.resumed; AppLifecycleState _appLifecycleState = AppLifecycleState.resumed;
@@ -31,7 +29,7 @@ void _onAppLifecycleChanged(AppLifecycleState state) {
Future<void> initializeLocalNotifications() async { Future<void> initializeLocalNotifications() async {
// Initialize Windows notification for Windows platform // Initialize Windows notification for Windows platform
windowsNotification = windows_notification.WindowsNotification( windowsNotification = winty.WindowsNotification(
applicationId: 'dev.solsynth.solian', applicationId: 'dev.solsynth.solian',
); );
@@ -61,7 +59,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
final notification = SnNotification.fromJson(pkt.data!); final notification = SnNotification.fromJson(pkt.data!);
if (_appLifecycleState == AppLifecycleState.resumed) { if (_appLifecycleState == AppLifecycleState.resumed) {
// App is focused, show in-app notification // App is focused, show in-app notification
log( talker.info(
'[Notification] Showing in-app notification: ${notification.title}', '[Notification] Showing in-app notification: ${notification.title}',
); );
showTopSnackBar( showTopSnackBar(
@@ -99,7 +97,7 @@ StreamSubscription<WebSocketPacket> setupNotificationListener(
); );
} else { } else {
// App is in background, show Windows system notification // App is in background, show Windows system notification
log( talker.info(
'[Notification] Showing Windows system notification: ${notification.title}', '[Notification] Showing Windows system notification: ${notification.title}',
); );
@@ -150,7 +148,7 @@ Future<void> subscribePushNotification(
_putTokenToRemote(apiClient, fcmToken, 1); _putTokenToRemote(apiClient, fcmToken, 1);
}) })
.onError((err) { .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) { if (deviceToken != null) {

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:archive/archive.dart'; 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:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:island/talker.dart';
/// Data model for a GitHub release we care about /// Data model for a GitHub release we care about
class GithubReleaseInfo { class GithubReleaseInfo {
@@ -121,40 +121,40 @@ class UpdateService {
/// Checks GitHub for the latest release and compares against the current app version. /// 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. /// If update is available, shows a bottom sheet with changelog and an action to open release page.
Future<void> checkForUpdates(BuildContext context) async { Future<void> checkForUpdates(BuildContext context) async {
log('[Update] Checking for updates...'); talker.info('[Update] Checking for updates...');
try { try {
final release = await fetchLatestRelease(); final release = await fetchLatestRelease();
if (release == null) { 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; return;
} }
log('[Update] Fetched latest release: ${release.tagName}'); talker.info('[Update] Fetched latest release: ${release.tagName}');
final info = await PackageInfo.fromPlatform(); final info = await PackageInfo.fromPlatform();
final localVersionStr = '${info.version}+${info.buildNumber}'; 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 latest = _ParsedVersion.tryParse(release.tagName);
final local = _ParsedVersion.tryParse(localVersionStr); final local = _ParsedVersion.tryParse(localVersionStr);
if (latest == null || local == null) { if (latest == null || local == null) {
log( talker.info(
'[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr', '[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr',
); );
// If parsing fails, do nothing silently // If parsing fails, do nothing silently
return; return;
} }
log('[Update] Parsed versions. Latest: $latest, Local: $local'); talker.info('[Update] Parsed versions. Latest: $latest, Local: $local');
final needsUpdate = latest.compareTo(local) > 0; final needsUpdate = latest.compareTo(local) > 0;
if (!needsUpdate) { if (!needsUpdate) {
log('[Update] App is up to date. No update needed.'); talker.info('[Update] App is up to date. No update needed.');
return; return;
} }
log('[Update] Update available! Latest: $latest, Local: $local'); talker.info('[Update] Update available! Latest: $latest, Local: $local');
if (!context.mounted) { if (!context.mounted) {
log('[Update] Context not mounted, cannot show update sheet.'); talker.info('[Update] Context not mounted, cannot show update sheet.');
return; return;
} }
@@ -163,10 +163,10 @@ class UpdateService {
if (context.mounted) { if (context.mounted) {
await showUpdateSheet(context, release); await showUpdateSheet(context, release);
log('[Update] Update sheet shown.'); talker.info('[Update] Update sheet shown.');
} }
} catch (e) { } catch (e) {
log('[Update] Error checking for updates: $e'); talker.error('[Update] Error checking for updates: $e');
// Ignore errors (network, api, etc.) // Ignore errors (network, api, etc.)
return; return;
} }
@@ -262,18 +262,18 @@ class UpdateService {
? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}' ? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}'
: _releasesLatestApi; : _releasesLatestApi;
log( talker.info(
'[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)', '[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)',
); );
final resp = await _dio.get(apiEndpoint); final resp = await _dio.get(apiEndpoint);
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
log( talker.error(
'[Update] Failed to fetch latest release. Status code: ${resp.statusCode}', '[Update] Failed to fetch latest release. Status code: ${resp.statusCode}',
); );
return null; return null;
} }
final data = resp.data as Map<String, dynamic>; 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 tagName = (data['tag_name'] ?? '').toString();
final name = (data['name'] ?? tagName).toString(); final name = (data['name'] ?? tagName).toString();
@@ -288,13 +288,13 @@ class UpdateService {
[]; [];
if (tagName.isEmpty || htmlUrl.isEmpty) { if (tagName.isEmpty || htmlUrl.isEmpty) {
log( talker.error(
'[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"', '[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"',
); );
return null; return null;
} }
log('[Update] Returning GithubReleaseInfo for tag: $tagName'); talker.info('[Update] Returning GithubReleaseInfo for tag: $tagName');
return GithubReleaseInfo( return GithubReleaseInfo(
tagName: tagName, tagName: tagName,
name: name, name: name,
@@ -380,7 +380,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
await File(zipPath).delete(); await File(zipPath).delete();
await Directory(extractDir).delete(recursive: true); await Directory(extractDir).delete(recursive: true);
} catch (e) { } catch (e) {
log('[Update] Error cleaning up temporary files: $e'); talker.error('[Update] Error cleaning up temporary files: $e');
} }
} catch (e) { } catch (e) {
_showError('Update failed: $e'); _showError('Update failed: $e');
@@ -437,7 +437,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
void Function(int received, int total)? onProgress, void Function(int received, int total)? onProgress,
}) async { }) async {
try { try {
log('[Update] Starting Windows installer download from: $url'); talker.info('[Update] Starting Windows installer download from: $url');
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final fileName = final fileName =
@@ -449,7 +449,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
filePath, filePath,
onReceiveProgress: (received, total) { onReceiveProgress: (received, total) {
if (total != -1) { if (total != -1) {
log( talker.info(
'[Update] Download progress: ${(received / total * 100).toStringAsFixed(1)}%', '[Update] Download progress: ${(received / total * 100).toStringAsFixed(1)}%',
); );
} }
@@ -458,16 +458,16 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
log('[Update] Windows installer downloaded successfully to: $filePath'); talker.info('[Update] Windows installer downloaded successfully to: $filePath');
return filePath; return filePath;
} else { } else {
log( talker.error(
'[Update] Failed to download Windows installer. Status: ${response.statusCode}', '[Update] Failed to download Windows installer. Status: ${response.statusCode}',
); );
return null; return null;
} }
} catch (e) { } catch (e) {
log('[Update] Error downloading Windows installer: $e'); talker.error('[Update] Error downloading Windows installer: $e');
return null; return null;
} }
} }
@@ -475,7 +475,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
/// Extracts the ZIP file to a temporary directory /// Extracts the ZIP file to a temporary directory
Future<String?> _extractWindowsInstaller(String zipPath) async { Future<String?> _extractWindowsInstaller(String zipPath) async {
try { try {
log('[Update] Extracting Windows installer from: $zipPath'); talker.info('[Update] Extracting Windows installer from: $zipPath');
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final extractDir = path.join( 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; return extractDir;
} catch (e) { } catch (e) {
log('[Update] Error extracting Windows installer: $e'); talker.error('[Update] Error extracting Windows installer: $e');
return null; return null;
} }
} }
@@ -511,7 +511,7 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
/// Runs the setup.exe file /// Runs the setup.exe file
Future<bool> _runWindowsInstaller(String extractDir) async { Future<bool> _runWindowsInstaller(String extractDir) async {
try { try {
log('[Update] Running Windows installer from: $extractDir'); talker.info('[Update] Running Windows installer from: $extractDir');
final dir = Directory(extractDir); final dir = Directory(extractDir);
final exeFiles = dir final exeFiles = dir
@@ -520,30 +520,30 @@ class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
.toList(); .toList();
if (exeFiles.isEmpty) { if (exeFiles.isEmpty) {
log('[Update] No .exe file found in extracted directory'); talker.info('[Update] No .exe file found in extracted directory');
return false; return false;
} }
final setupExePath = exeFiles.first.path; final setupExePath = exeFiles.first.path;
log('[Update] Found installer executable: $setupExePath'); talker.info('[Update] Found installer executable: $setupExePath');
final shell = Shell(); final shell = Shell();
final results = await shell.run(setupExePath); final results = await shell.run(setupExePath);
final result = results.first; final result = results.first;
if (result.exitCode == 0) { if (result.exitCode == 0) {
log('[Update] Windows installer completed successfully'); talker.info('[Update] Windows installer completed successfully');
return true; return true;
} else { } else {
log( talker.error(
'[Update] Windows installer failed with exit code: ${result.exitCode}', '[Update] Windows installer failed with exit code: ${result.exitCode}',
); );
log('[Update] Installer output: ${result.stdout}'); talker.error('[Update] Installer output: ${result.stdout}');
log('[Update] Installer errors: ${result.stderr}'); talker.error('[Update] Installer errors: ${result.stderr}');
return false; return false;
} }
} catch (e) { } catch (e) {
log('[Update] Error running Windows installer: $e'); talker.error('[Update] Error running Windows installer: $e');
return false; return false;
} }
} }
@@ -651,7 +651,7 @@ class _UpdateSheetState extends State<_UpdateSheet> {
Expanded( Expanded(
child: FilledButton.icon( child: FilledButton.icon(
onPressed: () { onPressed: () {
log(widget.androidUpdateUrl!); talker.info(widget.androidUpdateUrl!);
_installUpdate(widget.androidUpdateUrl!); _installUpdate(widget.androidUpdateUrl!);
}, },
icon: const Icon(Symbols.update), 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:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_platform_alert/flutter_platform_alert.dart'; import 'package:flutter_platform_alert/flutter_platform_alert.dart';
import 'package:island/talker.dart';
String _parseRemoteError(DioException err) { String _parseRemoteError(DioException err) {
log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}');
String? message; String? message;
if (err.response?.data is String) { if (err.response?.data is String) {
message = err.response?.data; message = err.response?.data;
@@ -30,7 +28,7 @@ String _parseRemoteError(DioException err) {
void showErrorAlert(dynamic err) async { void showErrorAlert(dynamic err) async {
if (err is Error) { if (err is Error) {
log('${err.stackTrace}'); talker.error('Something went wrong...', err, err.stackTrace);
} }
final text = switch (err) { final text = switch (err) {
String _ => err, String _ => err,

View File

@@ -1,13 +1,11 @@
// ignore_for_file: avoid_web_libraries_in_flutter // ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:developer';
import 'dart:js' as js; import 'dart:js' as js;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
String _parseRemoteError(DioException err) { String _parseRemoteError(DioException err) {
log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}');
String? message; String? message;
if (err.response?.data is String) { if (err.response?.data is String) {
message = err.response?.data; message = err.response?.data;

View File

@@ -1,11 +1,10 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/talker.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@@ -57,7 +56,7 @@ class _UniversalAudioState extends ConsumerState<UniversalAudio> {
String? uri; String? uri;
final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); final inCacheInfo = await DefaultCacheManager().getFileFromCache(url);
if (inCacheInfo == null) { if (inCacheInfo == null) {
log('[MediaPlayer] Miss cache: $url'); talker.info('[MediaPlayer] Miss cache: $url');
final token = ref.watch(tokenProvider)?.token; final token = ref.watch(tokenProvider)?.token;
DefaultCacheManager().downloadFile( DefaultCacheManager().downloadFile(
url, url,
@@ -66,7 +65,7 @@ class _UniversalAudioState extends ConsumerState<UniversalAudio> {
uri = url; uri = url;
} else { } else {
uri = inCacheInfo.file.path; uri = inCacheInfo.file.path;
log('[MediaPlayer] Hit cache: $url'); talker.info('[MediaPlayer] Hit cache: $url');
} }
_player!.open(Media(uri), play: widget.autoplay); _player!.open(Media(uri), play: widget.autoplay);

View File

@@ -1,10 +1,10 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
@@ -37,7 +37,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
String? uri; String? uri;
final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); final inCacheInfo = await DefaultCacheManager().getFileFromCache(url);
if (inCacheInfo == null) { if (inCacheInfo == null) {
log('[MediaPlayer] Miss cache: $url'); talker.info('[MediaPlayer] Miss cache: $url');
final token = ref.watch(tokenProvider)?.token; final token = ref.watch(tokenProvider)?.token;
DefaultCacheManager().downloadFile( DefaultCacheManager().downloadFile(
url, url,
@@ -46,7 +46,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
uri = url; uri = url;
} else { } else {
uri = inCacheInfo.file.path; uri = inCacheInfo.file.path;
log('[MediaPlayer] Hit cache: $url'); talker.info('[MediaPlayer] Hit cache: $url');
} }
_player!.open(Media(uri), play: widget.autoplay); _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:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:island/pods/config.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 { Future<void> _showSetTokenDialog(BuildContext context, WidgetRef ref) async {
final TextEditingController controller = TextEditingController(); final TextEditingController controller = TextEditingController();
@@ -115,6 +117,21 @@ class DebugSheet extends HookConsumerWidget {
}, },
), ),
const Divider(height: 8), 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( ListTile(
minTileHeight: 48, minTileHeight: 48,
leading: const Icon(Symbols.copy_all), leading: const Icon(Symbols.copy_all),

View File

@@ -1,6 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -9,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
@@ -66,7 +65,7 @@ class ComposeRecorder extends HookConsumerWidget {
useEffect(() { useEffect(() {
return () { return () {
// Called when widget is unmounted // Called when widget is unmounted
log('[Recorder] Clean up!'); talker.info('[Recorder] Clean up!');
originalAmplitude?.cancel(); originalAmplitude?.cancel();
amplitudeStream.close(); amplitudeStream.close();
record.dispose(); record.dispose();

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:dio/dio.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:island/pods/file_pool.dart';
import 'package:pasteboard/pasteboard.dart'; import 'package:pasteboard/pasteboard.dart';
import 'package:textfield_tags/textfield_tags.dart'; import 'package:textfield_tags/textfield_tags.dart';
import 'dart:async'; import 'package:island/talker.dart';
import 'dart:developer';
class ComposeState { class ComposeState {
final TextEditingController titleController; final TextEditingController titleController;
@@ -203,7 +204,7 @@ class ComposeLogic {
state.attachments.value = clone; state.attachments.value = clone;
} }
} catch (err) { } 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 // Continue with other attachments even if one fails
} }
} }
@@ -263,7 +264,7 @@ class ComposeLogic {
await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft); await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
} catch (e) { } 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); await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft);
} catch (e) { } 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()); showSnackBar('draftSaved'.tr());
} }
} catch (e) { } catch (e) {
log('[ComposeLogic] Failed to save draft manually, error: $e'); talker.error('[ComposeLogic] Failed to save draft manually, error: $e');
if (context.mounted) { if (context.mounted) {
showSnackBar('draftSaveFailed'.tr()); showSnackBar('draftSaveFailed'.tr());
} }