♻️ Refactor logging module
This commit is contained in:
		| @@ -730,5 +730,6 @@ | ||||
|   "forceUpdateDescription": "Force to show the application update popup, even the new version is not available.", | ||||
|   "debugLogging": "Runtime Logs", | ||||
|   "runtimeLogsOpen": "Open Logs", | ||||
|   "runtimeLogsDescription": "Show the runtime logs to help debugging." | ||||
|   "runtimeLogsDescription": "Show the runtime logs to help debugging.", | ||||
|   "signinResetPasswordHint": "Please enter the username / email address to help us to find your account and reset your password." | ||||
| } | ||||
|   | ||||
| @@ -728,5 +728,6 @@ | ||||
|   "forceUpdateDescription": "强制更新应用程序,即使有更新的版本可能不可用。", | ||||
|   "runtimeLogs": "运行时日志", | ||||
|   "runtimeLogsOpen": "打开日志文件", | ||||
|   "runtimeLogsDescription": "显示运行时的日志记录。" | ||||
|   "runtimeLogsDescription": "显示运行时的日志记录。", | ||||
|   "signinResetPasswordHint": "请输入用户名/电子邮箱地址以帮助我们找到您的帐户并重置密码。" | ||||
| } | ||||
|   | ||||
| @@ -728,5 +728,6 @@ | ||||
|   "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。", | ||||
|   "runtimeLogs": "運行時日誌", | ||||
|   "runtimeLogsOpen": "打開日誌文件", | ||||
|   "runtimeLogsDescription": "顯示運行時的日誌記錄。" | ||||
|   "runtimeLogsDescription": "顯示運行時的日誌記錄。", | ||||
|   "signinResetPasswordHint": "請輸入用户名/電子郵箱地址以幫助我們找到您的帳户並重置密碼。" | ||||
| } | ||||
|   | ||||
| @@ -728,5 +728,6 @@ | ||||
|   "forceUpdateDescription": "強制更新應用程序,即使有更新的版本可能不可用。", | ||||
|   "runtimeLogs": "運行時日誌", | ||||
|   "runtimeLogsOpen": "打開日誌文件", | ||||
|   "runtimeLogsDescription": "顯示運行時的日誌記錄。" | ||||
|   "runtimeLogsDescription": "顯示運行時的日誌記錄。", | ||||
|   "signinResetPasswordHint": "請輸入用戶名/電子郵箱地址以幫助我們找到您的帳戶並重置密碼。" | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| @@ -8,6 +7,7 @@ import 'package:drift/drift.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/database/database.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/database.dart'; | ||||
| import 'package:surface/providers/sn_attachment.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| @@ -532,7 +532,7 @@ class ChatMessageController extends ChangeNotifier { | ||||
|         }, | ||||
|       ).toJson(), | ||||
|     )); | ||||
|     log('[Messaging] Send read event request: $_readEventAnchor'); | ||||
|     logging.debug('[Messaging] Send read event request: $_readEventAnchor'); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   | ||||
| @@ -1,3 +1,10 @@ | ||||
| import 'package:talker/talker.dart'; | ||||
|  | ||||
| final logging = Talker(); | ||||
| final logging = Talker( | ||||
|   settings: TalkerSettings( | ||||
|     enabled: true, | ||||
|     useHistory: true, | ||||
|     maxHistoryItems: 1000, | ||||
|     useConsoleLogs: true, | ||||
|   ), | ||||
| ); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/link.dart'; | ||||
|  | ||||
| @@ -20,7 +20,7 @@ class SnLinkPreviewProvider { | ||||
|     final target = b64.encode(url); | ||||
|     if (_cache.containsKey(target)) return _cache[target]; | ||||
|  | ||||
|     log('[LinkPreview] Fetching $url ($target)'); | ||||
|     logging.debug('[LinkPreview] Fetching $url ($target)'); | ||||
|  | ||||
|     try { | ||||
|       final resp = await _sn.client.get('/cgi/re/link/$target'); | ||||
| @@ -28,7 +28,7 @@ class SnLinkPreviewProvider { | ||||
|       _cache[url] = meta; | ||||
|       return meta; | ||||
|     } catch (err) { | ||||
|       log('[LinkPreview] Failed to fetch $url ($target)...'); | ||||
|       logging.warning('[LinkPreview] Failed to fetch $url ($target)...', err); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| import 'dart:developer'; | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||
| @@ -9,6 +8,7 @@ import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_udid/flutter_udid.dart'; | ||||
| import 'package:local_notifier/local_notifier.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| @@ -48,11 +48,13 @@ class NotificationProvider extends ChangeNotifier { | ||||
|     var deviceUuid = await FlutterUdid.consistentUdid; | ||||
|  | ||||
|     if (deviceUuid.isEmpty) { | ||||
|       log("Unable to active push notifications, couldn't get device uuid"); | ||||
|       logging.warning( | ||||
|           '[Push Notification] Unable to active push notifications, couldn\'t get device uuid'); | ||||
|       return; | ||||
|     } else { | ||||
|       log('Device UUID is $deviceUuid'); | ||||
|       log('Registering device push notifications...'); | ||||
|       logging.info('[Push Notification] Device UUID is $deviceUuid'); | ||||
|       logging | ||||
|           .info('[Push Notification] Registering device push notifications...'); | ||||
|     } | ||||
|  | ||||
|     if (Platform.isIOS || Platform.isMacOS) { | ||||
| @@ -62,7 +64,7 @@ class NotificationProvider extends ChangeNotifier { | ||||
|       provider = 'fcm'; | ||||
|       token = await FirebaseMessaging.instance.getToken(); | ||||
|     } | ||||
|     log('Device Push Token is $token'); | ||||
|     logging.info('[Push Notification] Device Push Token is $token'); | ||||
|  | ||||
|     await _sn.client.post( | ||||
|       '/cgi/id/notifications/subscription', | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:dio/dio.dart'; | ||||
| @@ -11,9 +10,12 @@ import 'package:package_info_plus/package_info_plus.dart'; | ||||
| import 'package:device_info_plus/device_info_plus.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/widget.dart'; | ||||
| import 'package:synchronized/synchronized.dart'; | ||||
| import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart'; | ||||
| import 'package:talker_dio_logger/talker_dio_logger_settings.dart'; | ||||
|  | ||||
| const kNetworkServerDirectory = [ | ||||
|   ('Solar Network', 'https://api.sn.solsynth.dev'), | ||||
| @@ -36,6 +38,17 @@ class SnNetworkProvider { | ||||
|  | ||||
|     client = Dio(); | ||||
|  | ||||
|     client.interceptors.add( | ||||
|       TalkerDioLogger( | ||||
|         talker: logging, | ||||
|         settings: const TalkerDioLoggerSettings( | ||||
|           printRequestHeaders: false, | ||||
|           printResponseHeaders: false, | ||||
|           printResponseMessage: true, | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     client.interceptors.add(RetryInterceptor( | ||||
|       dio: client, | ||||
|       retries: 3, | ||||
| @@ -69,7 +82,6 @@ class SnNetworkProvider { | ||||
|       _prefs = _config.prefs; | ||||
|       client.options.baseUrl = _config.serverUrl; | ||||
|     }); | ||||
|  | ||||
|   } | ||||
|  | ||||
|   static Future<Dio> createOffContextClient() async { | ||||
| @@ -91,7 +103,8 @@ class SnNetworkProvider { | ||||
|           RequestOptions options, | ||||
|           RequestInterceptorHandler handler, | ||||
|         ) async { | ||||
|           final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey), prefs.getString(kRtkStoreKey), (atk, rtk) { | ||||
|           final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey), | ||||
|               prefs.getString(kRtkStoreKey), (atk, rtk) { | ||||
|             prefs.setString(kAtkStoreKey, atk); | ||||
|             prefs.setString(kRtkStoreKey, rtk); | ||||
|           }); | ||||
| @@ -103,7 +116,8 @@ class SnNetworkProvider { | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|     client.options.baseUrl = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; | ||||
|     client.options.baseUrl = | ||||
|         prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; | ||||
|  | ||||
|     return client; | ||||
|   } | ||||
| @@ -119,7 +133,8 @@ class SnNetworkProvider { | ||||
|       platformInfo = 'Web; ${deviceInfo.vendor}'; | ||||
|     } else if (Platform.isAndroid) { | ||||
|       final deviceInfo = await DeviceInfoPlugin().androidInfo; | ||||
|       platformInfo = 'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}'; | ||||
|       platformInfo = | ||||
|           'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}'; | ||||
|     } else if (Platform.isIOS) { | ||||
|       final deviceInfo = await DeviceInfoPlugin().iosInfo; | ||||
|       platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}'; | ||||
| @@ -128,7 +143,8 @@ class SnNetworkProvider { | ||||
|       platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}'; | ||||
|     } else if (Platform.isWindows) { | ||||
|       final deviceInfo = await DeviceInfoPlugin().windowsInfo; | ||||
|       platformInfo = 'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}'; | ||||
|       platformInfo = | ||||
|           'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}'; | ||||
|     } else if (Platform.isLinux) { | ||||
|       final deviceInfo = await DeviceInfoPlugin().linuxInfo; | ||||
|       platformInfo = 'Linux; ${deviceInfo.prettyName}'; | ||||
| @@ -148,12 +164,15 @@ class SnNetworkProvider { | ||||
|   final tkLock = Lock(); | ||||
|  | ||||
|   Future<String?> getFreshAtk() async { | ||||
|     return await _getFreshAtk(client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey), (atk, rtk) { | ||||
|     return await _getFreshAtk( | ||||
|         client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey), | ||||
|         (atk, rtk) { | ||||
|       setTokenPair(atk, rtk); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk, Function(String atk, String rtk)? onRefresh) async { | ||||
|   static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk, | ||||
|       Function(String atk, String rtk)? onRefresh) async { | ||||
|     if (_refreshCompleter != null) { | ||||
|       return await _refreshCompleter!.future; | ||||
|     } else { | ||||
| @@ -185,7 +204,8 @@ class SnNetworkProvider { | ||||
|         final payload = b64.decode(rawPayload); | ||||
|         final exp = jsonDecode(payload)['exp']; | ||||
|         if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) { | ||||
|           log('Access token need refresh, doing it at ${DateTime.now()}'); | ||||
|           logging.debug( | ||||
|               '[Auth] Access token need refresh, doing it at ${DateTime.now()}'); | ||||
|           final result = await _refreshToken(client.options.baseUrl, rtk); | ||||
|           if (result == null) { | ||||
|             atk = null; | ||||
| @@ -199,12 +219,12 @@ class SnNetworkProvider { | ||||
|           _refreshCompleter!.complete(atk); | ||||
|           return atk; | ||||
|         } else { | ||||
|           log('Access token refresh failed...'); | ||||
|           logging.error('[Auth] Access token refresh failed...'); | ||||
|           _refreshCompleter!.complete(null); | ||||
|         } | ||||
|       } | ||||
|     } catch (err) { | ||||
|       log('Failed to authenticate user: $err'); | ||||
|       logging.error('[Auth] Failed to authenticate user...', err); | ||||
|       _refreshCompleter!.completeError(err); | ||||
|     } finally { | ||||
|       _refreshCompleter = null; | ||||
| @@ -237,7 +257,8 @@ class SnNetworkProvider { | ||||
|     return result.$1; | ||||
|   } | ||||
|  | ||||
|   static Future<(String, String)?> _refreshToken(String baseUrl, String? rtk) async { | ||||
|   static Future<(String, String)?> _refreshToken( | ||||
|       String baseUrl, String? rtk) async { | ||||
|     if (rtk == null) return null; | ||||
|  | ||||
|     final dio = Dio(); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/attachment.dart'; | ||||
|  | ||||
| @@ -51,7 +50,7 @@ class SnStickerProvider { | ||||
|       return sticker; | ||||
|     } catch (err) { | ||||
|       _cache[alias] = null; | ||||
|       log('[Sticker] Failed to lookup sticker $alias: $err'); | ||||
|       logging.warning('[Sticker] Failed to lookup sticker $alias', err); | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
| @@ -66,7 +65,7 @@ class SnStickerProvider { | ||||
|         _cacheSticker(sticker); | ||||
|       } | ||||
|     } catch (err) { | ||||
|       log('[Sticker] Failed to list stickers: $err'); | ||||
|       logging.error('[Sticker] Failed to list stickers...', err); | ||||
|       rethrow; | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/config.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/account.dart'; | ||||
| @@ -30,8 +29,8 @@ class UserProvider extends ChangeNotifier { | ||||
|     notifyListeners(); | ||||
|     refreshUser().then((value) async { | ||||
|       if (value != null) { | ||||
|         log('Logged in as @${value.name}'); | ||||
|         log('Atk: ${await atk}'); | ||||
|         logging.info('[Auth] Logged in as @${value.name}'); | ||||
|         logging.debug('[Auth] Access token: ${await atk}'); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/types/websocket.dart'; | ||||
| @@ -30,7 +30,7 @@ class WebSocketProvider extends ChangeNotifier { | ||||
|     if (isConnected) return; | ||||
|     if (!_ua.isAuthorized) return; | ||||
|  | ||||
|     log('[WebSocket] Connecting to the server...'); | ||||
|     logging.debug('[WebSocket] Connecting to the server...'); | ||||
|     await connect(); | ||||
|   } | ||||
|  | ||||
| @@ -62,17 +62,14 @@ class WebSocketProvider extends ChangeNotifier { | ||||
|       await conn!.ready; | ||||
|       _wsStream = conn!.stream.asBroadcastStream(); | ||||
|       listen(); | ||||
|       log('[WebSocket] Connected to server!'); | ||||
|       logging.info('[WebSocket] Connected to server!'); | ||||
|       isConnected = true; | ||||
|     } catch (err) { | ||||
|       if (err is WebSocketChannelException) { | ||||
|         log('Failed to connect to websocket: ${(err.inner as dynamic).message}'); | ||||
|       } else { | ||||
|         log('Failed to connect to websocket: $err'); | ||||
|       } | ||||
|       logging.error('[WebSocket] Failed to connect to websocket...', err); | ||||
|  | ||||
|       if (!noRetry) { | ||||
|         log('Retry connecting to websocket in 3 seconds...'); | ||||
|         logging.warning( | ||||
|             '[WebSocket] Retry connecting to websocket in 3 seconds...'); | ||||
|         return Future.delayed( | ||||
|           const Duration(seconds: 3), | ||||
|           () => connect(noRetry: true), | ||||
| @@ -100,7 +97,8 @@ class WebSocketProvider extends ChangeNotifier { | ||||
|     _wsStream!.listen( | ||||
|       (event) { | ||||
|         final packet = WebSocketPackage.fromJson(jsonDecode(event)); | ||||
|         log('Websocket incoming message: ${packet.method} ${packet.message}'); | ||||
|         logging.debug( | ||||
|             '[Websocket] Incoming message: ${packet.method} ${packet.message}'); | ||||
|         pk.sink.add(packet); | ||||
|       }, | ||||
|       onDone: () { | ||||
|   | ||||
| @@ -1,11 +1,14 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:talker/talker.dart'; | ||||
| import 'package:talker_dio_logger/dio_logs.dart'; | ||||
| import 'package:talker_flutter/talker_flutter.dart'; | ||||
|  | ||||
| final Map<LogLevel, IconData> kLogLevelIcons = { | ||||
|   LogLevel.error: Symbols.error, | ||||
| @@ -16,15 +19,6 @@ final Map<LogLevel, IconData> kLogLevelIcons = { | ||||
|   LogLevel.verbose: Symbols.info_i, | ||||
| }; | ||||
|  | ||||
| final Map<LogLevel, Color> kLogLevelColors = { | ||||
|   LogLevel.error: Colors.red, | ||||
|   LogLevel.critical: Colors.red, | ||||
|   LogLevel.warning: Colors.orange, | ||||
|   LogLevel.info: Colors.blue, | ||||
|   LogLevel.debug: Colors.green, | ||||
|   LogLevel.verbose: Colors.green, | ||||
| }; | ||||
|  | ||||
| final Map<LogLevel, bool> kLogLevelFilled = { | ||||
|   LogLevel.error: false, | ||||
|   LogLevel.critical: true, | ||||
| @@ -39,43 +33,132 @@ class DebugLoggingScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final talkerTheme = TalkerScreenTheme.fromTheme(Theme.of(context)); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: const PageBackButton(), | ||||
|         title: Text('debugLogging').tr(), | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             onPressed: () { | ||||
|               logging.cleanHistory(); | ||||
|               Navigator.pop(context); | ||||
|             }, | ||||
|             icon: const Icon(Symbols.delete), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       body: SelectionArea( | ||||
|         child: ListView.builder( | ||||
|           padding: EdgeInsets.zero, | ||||
|           itemCount: logging.history.length, | ||||
|           itemBuilder: (context, index) { | ||||
|             final log = logging.history[index]; | ||||
|             return ListTile( | ||||
|               leading: Icon( | ||||
|                 kLogLevelIcons[log.logLevel ?? LogLevel.debug] ?? Symbols.help, | ||||
|                 color: kLogLevelColors[log.logLevel ?? LogLevel.debug], | ||||
|                 fill: (kLogLevelFilled[log.logLevel ?? LogLevel.debug] ?? false) | ||||
|                     ? 1 | ||||
|                     : 0, | ||||
|               ), | ||||
|               title: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|       body: ListView.builder( | ||||
|         reverse: true, | ||||
|         padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), | ||||
|         itemCount: logging.history.length, | ||||
|         itemBuilder: (context, index) { | ||||
|           final log = logging.history[index]; | ||||
|           final color = log.getFlutterColor(talkerTheme); | ||||
|           return ListTile( | ||||
|             minTileHeight: 0, | ||||
|             tileColor: color.withOpacity(0.2), | ||||
|             leading: Icon( | ||||
|               kLogLevelIcons[log.logLevel ?? LogLevel.debug] ?? Symbols.help, | ||||
|               color: color, | ||||
|               fill: (kLogLevelFilled[log.logLevel ?? LogLevel.debug] ?? false) | ||||
|                   ? 1 | ||||
|                   : 0, | ||||
|             ), | ||||
|             title: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 if (log is DioRequestLog) | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         '${log.requestOptions.method} ${log.displayMessage}', | ||||
|                         style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                       ), | ||||
|                       Theme( | ||||
|                         data: Theme.of(context).copyWith( | ||||
|                           dividerColor: Colors.transparent, | ||||
|                         ), | ||||
|                         child: ExpansionTile( | ||||
|                           title: Text('Payload').fontSize(13), | ||||
|                           minTileHeight: 0, | ||||
|                           tilePadding: EdgeInsets.zero, | ||||
|                           expandedCrossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               log.requestOptions.data.toString(), | ||||
|                               style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ) | ||||
|                 else if (log is DioResponseLog) | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         '${log.response.statusCode} ${log.displayMessage}', | ||||
|                         style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                       ), | ||||
|                       Theme( | ||||
|                         data: Theme.of(context).copyWith( | ||||
|                           dividerColor: Colors.transparent, | ||||
|                         ), | ||||
|                         child: ExpansionTile( | ||||
|                           title: Text('Payload').fontSize(13), | ||||
|                           minTileHeight: 0, | ||||
|                           tilePadding: EdgeInsets.zero, | ||||
|                           expandedCrossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               log.response.data.toString(), | ||||
|                               style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ) | ||||
|                 else | ||||
|                   Text( | ||||
|                     log.message ?? 'unknown'.tr(), | ||||
|                     log.displayMessage, | ||||
|                     style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                   ), | ||||
|                   if (log.error != null) | ||||
|                     Text( | ||||
|                       log.error!.toString(), | ||||
|                       style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                     ).bold(), | ||||
|                 ], | ||||
|               ), | ||||
|               subtitle: Text(log.time.toString()).fontSize(11), | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|                 if (log.exception != null) | ||||
|                   Text( | ||||
|                     log.displayException, | ||||
|                     style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                   ).bold(), | ||||
|                 if (log.error != null) | ||||
|                   Text( | ||||
|                     log.displayException, | ||||
|                     style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                   ).bold(), | ||||
|                 if (log.stackTrace != null) | ||||
|                   Text( | ||||
|                     log.displayStackTrace, | ||||
|                     style: GoogleFonts.robotoMono(fontSize: 12), | ||||
|                   ).padding(top: 4), | ||||
|               ], | ||||
|             ), | ||||
|             subtitle: Text( | ||||
|               '${(log.title?.replaceAll('-', ' ') ?? 'default').capitalizeEachWord()} · ${log.displayTime()}', | ||||
|             ).fontSize(11), | ||||
|             onTap: () { | ||||
|               Clipboard.setData( | ||||
|                 ClipboardData( | ||||
|                   text: ['[${log.time}]', log.message, log.error?.toString()] | ||||
|                       .where((ele) => ele != null) | ||||
|                       .join('\n'), | ||||
|                 ), | ||||
|               ); | ||||
|             }, | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:cross_file/cross_file.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| @@ -8,6 +7,7 @@ import 'package:gap/gap.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/controllers/post_write_controller.dart'; | ||||
| import 'package:surface/logger.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:video_compress/video_compress.dart'; | ||||
|  | ||||
| @@ -17,10 +17,12 @@ class PendingVideoCompressDialog extends StatefulWidget { | ||||
|   const PendingVideoCompressDialog({super.key, required this.media}); | ||||
|  | ||||
|   @override | ||||
|   State<PendingVideoCompressDialog> createState() => _PendingVideoCompressDialogState(); | ||||
|   State<PendingVideoCompressDialog> createState() => | ||||
|       _PendingVideoCompressDialogState(); | ||||
| } | ||||
|  | ||||
| class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog> { | ||||
| class _PendingVideoCompressDialogState | ||||
|     extends State<PendingVideoCompressDialog> { | ||||
|   VideoQuality _quality = VideoQuality.DefaultQuality; | ||||
|  | ||||
|   bool _isBusy = false; | ||||
| @@ -50,7 +52,7 @@ class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog> | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _progressSubscription = VideoCompress.compressProgress$.subscribe((event) { | ||||
|       log('[Compress] Progress: $event'); | ||||
|       logging.debug('[Paperclip.VideoCompress] Progress: $event'); | ||||
|       setState(() { | ||||
|         _progress = event / 100; | ||||
|         _isBusy = event < 100; | ||||
| @@ -132,7 +134,9 @@ class _PendingVideoCompressDialogState extends State<PendingVideoCompressDialog> | ||||
|             ), | ||||
|           ), | ||||
|           const Gap(8), | ||||
|           Text('attachmentCompressQualityHint', style: Theme.of(context).textTheme.bodySmall!).tr(), | ||||
|           Text('attachmentCompressQualityHint', | ||||
|                   style: Theme.of(context).textTheme.bodySmall!) | ||||
|               .tr(), | ||||
|           if (_isBusy) | ||||
|             TweenAnimationBuilder<double>( | ||||
|               tween: Tween(begin: 0, end: _progress ?? 0), | ||||
|   | ||||
| @@ -133,7 +133,8 @@ extension AppPromptExtension on BuildContext { | ||||
|                     ), | ||||
|                     recognizer: TapGestureRecognizer() | ||||
|                       ..onTap = () { | ||||
|                         launchUrlString('https://kb.solsynth.dev/solar-network'); | ||||
|                         launchUrlString( | ||||
|                             'https://kb.solsynth.dev/solar-network'); | ||||
|                       }, | ||||
|                   ), | ||||
|                 ], | ||||
| @@ -157,7 +158,17 @@ extension ByteFormatter on int { | ||||
|     if (this == 0) return '0 Bytes'; | ||||
|     const k = 1024; | ||||
|     final dm = decimals < 0 ? 0 : decimals; | ||||
|     final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; | ||||
|     final sizes = [ | ||||
|       'Bytes', | ||||
|       'KiB', | ||||
|       'MiB', | ||||
|       'GiB', | ||||
|       'TiB', | ||||
|       'PiB', | ||||
|       'EiB', | ||||
|       'ZiB', | ||||
|       'YiB' | ||||
|     ]; | ||||
|     final i = (math.log(this) / math.log(k)).floor().toInt(); | ||||
|     return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; | ||||
|   } | ||||
| @@ -167,4 +178,15 @@ extension StringFormatter on String { | ||||
|   String capitalize() { | ||||
|     return "${this[0].toUpperCase()}${substring(1)}"; | ||||
|   } | ||||
|  | ||||
|   String capitalizeEachWord() { | ||||
|     if (isEmpty) { | ||||
|       return this; | ||||
|     } | ||||
|     return split(' ') | ||||
|         .map((word) => word.isNotEmpty | ||||
|             ? '${word[0].toUpperCase()}${word.substring(1)}' | ||||
|             : '') | ||||
|         .join(' '); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user