diff --git a/feature/crashes/presentation/src/main/kotlin/com/f0x1d/logfox/feature/crashes/presentation/details/CrashDetailsEffectHandler.kt b/feature/crashes/presentation/src/main/kotlin/com/f0x1d/logfox/feature/crashes/presentation/details/CrashDetailsEffectHandler.kt index 8479ddea..56df87ba 100644 --- a/feature/crashes/presentation/src/main/kotlin/com/f0x1d/logfox/feature/crashes/presentation/details/CrashDetailsEffectHandler.kt +++ b/feature/crashes/presentation/src/main/kotlin/com/f0x1d/logfox/feature/crashes/presentation/details/CrashDetailsEffectHandler.kt @@ -11,7 +11,7 @@ import com.f0x1d.logfox.feature.datetime.api.DateTimeFormatter import com.f0x1d.logfox.feature.preferences.api.domain.crashes.GetUseSeparateNotificationsChannelsForCrashesFlowUseCase import com.f0x1d.logfox.feature.preferences.api.domain.crashes.GetWrapCrashLogLinesFlowUseCase import com.f0x1d.logfox.feature.preferences.api.domain.crashes.SetWrapCrashLogLinesUseCase -import com.f0x1d.logfox.feature.preferences.api.domain.service.GetExportLogsAsTxtUseCase +import com.f0x1d.logfox.feature.preferences.api.domain.service.GetLogFileExtensionUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import javax.inject.Inject @@ -26,7 +26,7 @@ internal class CrashDetailsEffectHandler @Inject constructor( private val getWrapCrashLogLinesFlowUseCase: GetWrapCrashLogLinesFlowUseCase, private val setWrapCrashLogLinesUseCase: SetWrapCrashLogLinesUseCase, private val getUseSeparateNotificationsChannelsForCrashesFlowUseCase: GetUseSeparateNotificationsChannelsForCrashesFlowUseCase, - private val getExportLogsAsTxtUseCase: GetExportLogsAsTxtUseCase, + private val getLogFileExtensionUseCase: GetLogFileExtensionUseCase, private val dateTimeFormatter: DateTimeFormatter, ) : EffectHandler { @OptIn(ExperimentalCoroutinesApi::class) @@ -62,8 +62,7 @@ internal class CrashDetailsEffectHandler @Inject constructor( } is CrashDetailsSideEffect.PrepareFileExport -> { - val extension = if (getExportLogsAsTxtUseCase()) "txt" else "log" - val filename = exportFilename(effect.packageName, effect.dateAndTime, extension) + val filename = exportFilename(effect.packageName, effect.dateAndTime, getLogFileExtensionUseCase()) onCommand(CrashDetailsCommand.FileExportPickerReady(filename)) } diff --git a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsCommand.kt b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsCommand.kt index aaac7fc3..ebee0693 100644 --- a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsCommand.kt +++ b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsCommand.kt @@ -36,6 +36,12 @@ internal sealed interface LogsCommand { data class ExportPickerReady(val filename: String) : LogsCommand + data object SaveCurrentLogsClicked : LogsCommand + + data class SaveCurrentPickerReady(val filename: String) : LogsCommand + + data class SaveCurrentLogsTo(val uri: Uri) : LogsCommand + data object SwitchState : LogsCommand data object Pause : LogsCommand diff --git a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsEffectHandler.kt b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsEffectHandler.kt index 4ca20379..7055f416 100644 --- a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsEffectHandler.kt +++ b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsEffectHandler.kt @@ -14,7 +14,7 @@ import com.f0x1d.logfox.feature.logging.api.presentation.LoggingServiceDelegate import com.f0x1d.logfox.feature.preferences.api.domain.logs.GetLogsExpandedFlowUseCase import com.f0x1d.logfox.feature.preferences.api.domain.logs.GetLogsTextSizeFlowUseCase import com.f0x1d.logfox.feature.preferences.api.domain.logs.GetResumeLoggingWithBottomTouchFlowUseCase -import com.f0x1d.logfox.feature.preferences.api.domain.service.GetExportLogsAsTxtUseCase +import com.f0x1d.logfox.feature.preferences.api.domain.service.GetLogFileExtensionUseCase import com.f0x1d.logfox.feature.recordings.api.domain.CreateRecordingFromLinesUseCase import kotlinx.coroutines.flow.combine import javax.inject.Inject @@ -33,7 +33,7 @@ internal class LogsEffectHandler @Inject constructor( private val formatLogLineUseCase: FormatLogLineUseCase, private val exportLogsToUriUseCase: ExportLogsToUriUseCase, private val loggingServiceDelegate: LoggingServiceDelegate, - private val getExportLogsAsTxtUseCase: GetExportLogsAsTxtUseCase, + private val getLogFileExtensionUseCase: GetLogFileExtensionUseCase, private val dateTimeFormatter: DateTimeFormatter, ) : EffectHandler { @@ -88,11 +88,15 @@ internal class LogsEffectHandler @Inject constructor( } is LogsSideEffect.PrepareExport -> { - val extension = if (getExportLogsAsTxtUseCase()) "txt" else "log" - val filename = "${dateTimeFormatter.formatForExport(System.currentTimeMillis())}.$extension" + val filename = "${dateTimeFormatter.formatForExport(System.currentTimeMillis())}.${getLogFileExtensionUseCase()}" onCommand(LogsCommand.ExportPickerReady(filename)) } + is LogsSideEffect.PrepareSaveCurrentLogs -> { + val filename = "${dateTimeFormatter.formatForExport(System.currentTimeMillis())}.${getLogFileExtensionUseCase()}" + onCommand(LogsCommand.SaveCurrentPickerReady(filename)) + } + is LogsSideEffect.FormatAndCopyLog -> { val formattedText = formatLogLineUseCase( logLine = effect.logLine, diff --git a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsReducer.kt b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsReducer.kt index 351d8bde..f67452eb 100644 --- a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsReducer.kt +++ b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsReducer.kt @@ -116,6 +116,25 @@ internal class LogsReducer @Inject constructor() : Reducer { + state.withSideEffects(LogsSideEffect.PrepareSaveCurrentLogs) + } + + is LogsCommand.SaveCurrentPickerReady -> { + state.withSideEffects( + LogsSideEffect.LaunchSaveCurrentPicker(filename = command.filename), + ) + } + + is LogsCommand.SaveCurrentLogsTo -> { + state.withSideEffects( + LogsSideEffect.ExportLogsTo( + uri = command.uri, + lines = state.logs, + ), + ) + } + is LogsCommand.SwitchState -> { state.copy( paused = !state.paused, diff --git a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsSideEffect.kt b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsSideEffect.kt index a4166260..506ae448 100644 --- a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsSideEffect.kt +++ b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/LogsSideEffect.kt @@ -22,6 +22,8 @@ internal sealed interface LogsSideEffect { data object PrepareExport : LogsSideEffect + data object PrepareSaveCurrentLogs : LogsSideEffect + // UI side effects - handled by Fragment data object NavigateToRecordings : LogsSideEffect @@ -47,6 +49,8 @@ internal sealed interface LogsSideEffect { data class LaunchExportPicker(val filename: String) : LogsSideEffect + data class LaunchSaveCurrentPicker(val filename: String) : LogsSideEffect + data object ClearLogs : LogsSideEffect data object RestartLogging : LogsSideEffect diff --git a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/ui/LogsFragment.kt b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/ui/LogsFragment.kt index bfc111f6..5bc6bb38 100644 --- a/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/ui/LogsFragment.kt +++ b/feature/logging/presentation/src/main/kotlin/com/f0x1d/logfox/feature/logging/presentation/list/ui/LogsFragment.kt @@ -77,6 +77,12 @@ internal class LogsFragment : it?.let { uri -> send(LogsCommand.ExportSelectedTo(uri)) } } + private val saveCurrentLogsLauncher = registerForActivityResult( + ActivityResultContracts.CreateDocument("text/*"), + ) { + it?.let { uri -> send(LogsCommand.SaveCurrentLogsTo(uri)) } + } + override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentLogsBinding.inflate(inflater, container, false) override fun FragmentLogsBinding.onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -119,6 +125,9 @@ internal class LogsFragment : setClickListenerOn(R.id.export_selected_item) { send(LogsCommand.ExportSelectedClicked) } + setClickListenerOn(R.id.save_current_log_item) { + send(LogsCommand.SaveCurrentLogsClicked) + } setClickListenerOn(R.id.clear_item) { send(LogsCommand.ClearLogs) } @@ -233,6 +242,10 @@ internal class LogsFragment : exportLogsLauncher.launch(sideEffect.filename) } + is LogsSideEffect.LaunchSaveCurrentPicker -> { + saveCurrentLogsLauncher.launch(sideEffect.filename) + } + else -> Unit } } @@ -311,6 +324,7 @@ internal class LogsFragment : invisibleDuringSelection(R.id.search_item) invisibleDuringSelection(R.id.filters_item) visibleDuringSelection(R.id.selected_item) + visibleOnlyInDefault(R.id.save_current_log_item) visibleOnlyInDefault(R.id.clear_item) visibleOnlyInDefault(R.id.restart_logging_item) visibleOnlyInDefault(R.id.exit_item) diff --git a/feature/logging/presentation/src/main/res/menu/logs_menu.xml b/feature/logging/presentation/src/main/res/menu/logs_menu.xml index 3a228915..9b19679b 100644 --- a/feature/logging/presentation/src/main/res/menu/logs_menu.xml +++ b/feature/logging/presentation/src/main/res/menu/logs_menu.xml @@ -53,6 +53,12 @@ + + { private val titleUpdateMutex = Mutex() @@ -43,8 +43,7 @@ internal class RecordingDetailsEffectHandler @Inject constructor( is RecordingDetailsSideEffect.PrepareFileExport -> { val recording = getRecordingByIdFlowUseCase(recordingId).firstOrNull() ?: return - val extension = if (getExportLogsAsTxtUseCase()) "txt" else "log" - val filename = "${dateTimeFormatter.formatForExport(recording.dateAndTime)}.$extension" + val filename = "${dateTimeFormatter.formatForExport(recording.dateAndTime)}.${getLogFileExtensionUseCase()}" onCommand(RecordingDetailsCommand.FileExportPickerReady(filename)) }