diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index d5b0324b..b9d54a93 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -78,7 +78,9 @@ "pageTitle": "logs", "clearLogsButtonText": "clear", "filterHint": "filter", - "allLevelsFilter": "all" + "allLevelsFilter": "all", + "shareCoreLogs": "share core logs", + "shareAppLogs": "share app logs" }, "settings": { "pageTitle": "settings", @@ -120,7 +122,9 @@ "miscellaneous": { "sectionTitle": "miscellaneous", "connectionTestUrl": "connection test url", - "concurrentTestCount": "concurrent test count" + "concurrentTestCount": "concurrent test count", + "debugMode": "debug mode", + "debugModeMsg": "restart the app for this to take effect" } }, "about": { diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 594e9d49..6a14c357 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -78,7 +78,9 @@ "pageTitle": "لاگ‌ها", "clearLogsButtonText": "پاک‌سازی", "filterHint": "فیلتر", - "allLevelsFilter": "همه" + "allLevelsFilter": "همه", + "shareCoreLogs": "اشتراک‌گذاری لاگ هسته", + "shareAppLogs": "اشتراک‌گذاری لاگ برنامه" }, "settings": { "pageTitle": "تنظیمات", @@ -120,7 +122,9 @@ "miscellaneous": { "sectionTitle": "متفرقه", "connectionTestUrl": "لینک تست کانکشن", - "concurrentTestCount": "شمار تست همزمان" + "concurrentTestCount": "شمار تست همزمان", + "debugMode": "دیباگ مود", + "debugModeMsg": "برای اعمال این تغییر اپ را ری‌استارت کنید" } }, "about": { diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 747d8fb9..8b865fb3 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:hiddify/core/app/app.dart'; +import 'package:hiddify/core/prefs/misc_prefs.dart'; import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/data/data_providers.dart'; import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart'; @@ -41,10 +42,12 @@ Future lazyBootstrap(WidgetsBinding widgetsBinding) async { overrides: [sharedPreferencesProvider.overrideWithValue(sharedPreferences)], ); + final debug = container.read(debugModeProvider) || kDebugMode; + final filesEditor = container.read(filesEditorServiceProvider); await filesEditor.init(); - initLoggers(container.read); + initLoggers(container.read, debug); _loggy.info( "os: ${Platform.operatingSystem}(${Platform.operatingSystemVersion})", ); @@ -76,9 +79,10 @@ Future lazyBootstrap(WidgetsBinding widgetsBinding) async { void initLoggers( Result Function(ProviderListenable) read, + bool debug, ) { - const logLevel = kDebugMode ? LogLevel.all : LogLevel.info; - final logToFile = !Platform.isAndroid && !Platform.isIOS; + final logLevel = debug ? LogLevel.all : LogLevel.info; + final logToFile = debug || (!Platform.isAndroid && !Platform.isIOS); Loggy.initLoggy( logPrinter: MultiLogPrinter( const PrettyPrinter(), @@ -86,8 +90,7 @@ void initLoggers( ? FileLogPrinter(read(filesEditorServiceProvider).appLogsPath) : null, ), - // ignore: avoid_redundant_argument_values - logOptions: const LogOptions(logLevel), + logOptions: LogOptions(logLevel), ); } diff --git a/lib/core/prefs/misc_prefs.dart b/lib/core/prefs/misc_prefs.dart index 7d27a50f..beffa91d 100644 --- a/lib/core/prefs/misc_prefs.dart +++ b/lib/core/prefs/misc_prefs.dart @@ -8,3 +8,5 @@ final concurrentTestCountProvider = PrefNotifier.provider( "concurrent_test_count", Defaults.concurrentTestCount, ); + +final debugModeProvider = PrefNotifier.provider("debug_mode", false); diff --git a/lib/data/repository/core_facade_impl.dart b/lib/data/repository/core_facade_impl.dart index 80e30aec..d10a61e6 100644 --- a/lib/data/repository/core_facade_impl.dart +++ b/lib/data/repository/core_facade_impl.dart @@ -98,7 +98,7 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade { @override Stream> watchLogs() { return singbox - .watchLogs(filesEditor.logsPath) + .watchLogs(filesEditor.coreLogsPath) .handleExceptions(CoreServiceFailure.unexpected); } diff --git a/lib/features/logs/view/logs_page.dart b/lib/features/logs/view/logs_page.dart index f704172c..13882d44 100644 --- a/lib/features/logs/view/logs_page.dart +++ b/lib/features/logs/view/logs_page.dart @@ -1,16 +1,22 @@ +import 'dart:io'; + import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; import 'package:fpdart/fpdart.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/core/prefs/misc_prefs.dart'; import 'package:hiddify/domain/clash/clash.dart'; import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/features/common/common.dart'; import 'package:hiddify/features/logs/notifier/notifier.dart'; +import 'package:hiddify/services/service_providers.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; +import 'package:share_plus/share_plus.dart'; import 'package:tint/tint.dart'; +import 'package:url_launcher/url_launcher.dart'; class LogsPage extends HookConsumerWidget { const LogsPage({super.key}); @@ -21,6 +27,45 @@ class LogsPage extends HookConsumerWidget { final asyncState = ref.watch(logsNotifierProvider); final notifier = ref.watch(logsNotifierProvider.notifier); + final debug = ref.watch(debugModeProvider); + + final List popupButtons = debug || PlatformUtils.isDesktop + ? [ + PopupMenuItem( + child: Text(t.logs.shareCoreLogs.sentenceCase), + onTap: () async { + if (Platform.isWindows || Platform.isLinux) { + await launchUrl( + ref.read(filesEditorServiceProvider).logsDir.uri, + ); + } else { + final file = XFile( + ref.read(filesEditorServiceProvider).coreLogsPath, + mimeType: "text/plain", + ); + await Share.shareXFiles([file]); + } + }, + ), + PopupMenuItem( + child: Text(t.logs.shareAppLogs.sentenceCase), + onTap: () async { + if (Platform.isWindows || Platform.isLinux) { + await launchUrl( + ref.read(filesEditorServiceProvider).logsDir.uri, + ); + } else { + final file = XFile( + ref.read(filesEditorServiceProvider).appLogsPath, + mimeType: "text/plain", + ); + await Share.shareXFiles([file]); + } + }, + ), + ] + : []; + switch (asyncState) { case AsyncData(value: final state): return Scaffold( @@ -28,6 +73,14 @@ class LogsPage extends HookConsumerWidget { // TODO: fix height toolbarHeight: 90, title: Text(t.logs.pageTitle.titleCase), + actions: [ + if (popupButtons.isNotEmpty) + PopupMenuButton( + itemBuilder: (context) { + return popupButtons; + }, + ), + ], bottom: PreferredSize( preferredSize: const Size.fromHeight(36), child: Padding( diff --git a/lib/features/settings/widgets/miscellaneous_setting_tiles.dart b/lib/features/settings/widgets/miscellaneous_setting_tiles.dart index cfc2d0a7..9aaeea1b 100644 --- a/lib/features/settings/widgets/miscellaneous_setting_tiles.dart +++ b/lib/features/settings/widgets/miscellaneous_setting_tiles.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/core/prefs/misc_prefs.dart'; import 'package:hiddify/domain/constants.dart'; @@ -16,6 +17,7 @@ class MiscellaneousSettingTiles extends HookConsumerWidget { final connectionTestUrl = ref.watch(connectionTestUrlProvider); final concurrentTestCount = ref.watch(concurrentTestCountProvider); + final debug = ref.watch(debugModeProvider); return Column( children: [ @@ -37,7 +39,7 @@ class MiscellaneousSettingTiles extends HookConsumerWidget { subtitle: Text(concurrentTestCount.toString()), onTap: () async { final val = await SettingsInputDialog( - title: t.settings.miscellaneous.connectionTestUrl.titleCase, + title: t.settings.miscellaneous.concurrentTestCount.titleCase, initialValue: concurrentTestCount, resetValue: Defaults.concurrentTestCount, mapTo: (value) => int.tryParse(value), @@ -47,6 +49,34 @@ class MiscellaneousSettingTiles extends HookConsumerWidget { await ref.read(concurrentTestCountProvider.notifier).update(val); }, ), + SwitchListTile( + title: Text(t.settings.miscellaneous.debugMode.titleCase), + value: debug, + onChanged: (value) async { + if (value) { + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(t.settings.miscellaneous.debugMode.titleCase), + content: Text( + t.settings.miscellaneous.debugModeMsg.sentenceCase, + ), + actions: [ + TextButton( + onPressed: () => context.pop(true), + child: Text( + MaterialLocalizations.of(context).okButtonLabel, + ), + ), + ], + ); + }, + ); + } + await ref.read(debugModeProvider.notifier).update(value); + }, + ), ], ); } diff --git a/lib/services/files_editor_service.dart b/lib/services/files_editor_service.dart index 19154d1f..183da987 100644 --- a/lib/services/files_editor_service.dart +++ b/lib/services/files_editor_service.dart @@ -11,6 +11,7 @@ class FilesEditorService with InfraLogger { late final Directory baseDir; late final Directory workingDir; late final Directory tempDir; + late final Directory logsDir; late final Directory _configsDir; Future init() async { @@ -24,10 +25,12 @@ class FilesEditorService with InfraLogger { workingDir = await getApplicationDocumentsDirectory(); } tempDir = await getTemporaryDirectory(); + logsDir = workingDir; loggy.debug("base dir: ${baseDir.path}"); loggy.debug("working dir: ${workingDir.path}"); loggy.debug("temp dir: ${tempDir.path}"); + loggy.debug("logs dire: ${logsDir.path}"); _configsDir = Directory(p.join(workingDir.path, Constants.configsFolderName)); @@ -49,13 +52,12 @@ class FilesEditorService with InfraLogger { } await _populateGeoAssets(); - if (PlatformUtils.isDesktop) { - final logFile = File(logsPath); - if (await logFile.exists()) { - await logFile.writeAsString(""); - } else { - await logFile.create(recursive: true); - } + + final coreLogFile = File(coreLogsPath); + if (await coreLogFile.exists()) { + await coreLogFile.writeAsString(""); + } else { + await coreLogFile.create(recursive: true); } } @@ -68,8 +70,8 @@ class FilesEditorService with InfraLogger { return getApplicationDocumentsDirectory(); } - String get appLogsPath => p.join(workingDir.path, "app.log"); - String get logsPath => p.join(workingDir.path, "box.log"); + String get appLogsPath => p.join(logsDir.path, "app.log"); + String get coreLogsPath => p.join(logsDir.path, "box.log"); String configPath(String fileName) { return p.join(_configsDir.path, "$fileName.json");