From 96c6f4e6df1fc8226d0000e961ce191c07057962 Mon Sep 17 00:00:00 2001 From: T8RIN Date: Sun, 26 May 2024 02:32:37 +0300 Subject: [PATCH] Template filters finished by #1097, also mastered saving to uri --- .../core/data/image/AndroidShareProvider.kt | 41 +++--------- .../core/data/saving/AndroidFileController.kt | 21 ++++--- .../core/data/saving/FileWriteable.kt | 4 +- .../core/data/saving/StreamWriteable.kt | 2 +- .../core/domain/saving/FileController.kt | 3 +- .../core/ui/utils/helper/ContextUtils.kt | 14 ----- .../core/ui/utils/helper/ParseSaveResult.kt | 27 ++++++++ .../presentation/ApngToolsScreen.kt | 39 +++--------- .../viewModel/ApngToolsViewModel.kt | 16 ++--- .../cipher/presentation/FileCipherScreen.kt | 38 +++--------- .../viewModel/FileCipherViewModel.kt | 19 +++--- .../presentation/DocumentScannerScreen.kt | 33 +++------- .../viewModel/DocumentScannerViewModel.kt | 23 +++---- .../components/AddEditMaskSheet.kt | 6 -- .../components/AddFiltersSheet.kt | 62 +++++++------------ .../gif_tools/presentation/GifToolsScreen.kt | 39 +++--------- .../viewModel/GifToolsViewModel.kt | 16 ++--- .../pdf_tools/presentation/PdfToolsScreen.kt | 37 +++-------- .../viewModel/PdfToolsViewModel.kt | 16 ++--- .../presentation/components/SettingItem.kt | 31 ++++------ .../viewModel/SettingsViewModel.kt | 14 ++--- .../feature/zip/presentation/ZipScreen.kt | 38 +++--------- .../presentation/viewModel/ZipViewModel.kt | 19 +++--- 23 files changed, 202 insertions(+), 356 deletions(-) diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/image/AndroidShareProvider.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/image/AndroidShareProvider.kt index 30679470a0..0a052edd26 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/image/AndroidShareProvider.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/image/AndroidShareProvider.kt @@ -40,7 +40,6 @@ import ru.tech.imageresizershrinker.core.domain.saving.model.ImageSaveTarget import ru.tech.imageresizershrinker.core.domain.saving.use import ru.tech.imageresizershrinker.core.resources.R import java.io.File -import java.io.FileOutputStream import javax.inject.Inject internal class AndroidShareProvider @Inject constructor( @@ -155,25 +154,10 @@ internal class AndroidShareProvider @Inject constructor( byteArray: ByteArray, filename: String ): String? = withContext(ioDispatcher) { - val imagesFolder = File(context.cacheDir, "files") - - runCatching { - imagesFolder.mkdirs() - val file = File(imagesFolder, filename) - FileOutputStream(file).use { - it.write(byteArray) - } - FileProvider.getUriForFile(context, context.getString(R.string.file_provider), file) - .also { uri -> - runCatching { - context.grantUriPermission( - context.packageName, - uri, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION - ) - } - } - }.getOrNull()?.toString() + cacheData( + writeData = { it.writeBytes(byteArray) }, + filename = filename, + ) } override suspend fun shareByteArray( @@ -181,18 +165,11 @@ internal class AndroidShareProvider @Inject constructor( filename: String, onComplete: () -> Unit ) = withContext(ioDispatcher) { - cacheByteArray( - byteArray = byteArray, - filename = filename - )?.let { - shareUri( - uri = it, - type = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension( - imageGetter.getExtension(it) - ) ?: "*/*" - ) - } + shareData( + writeData = { it.writeBytes(byteArray) }, + filename = filename, + onComplete = onComplete + ) onComplete() } diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/AndroidFileController.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/AndroidFileController.kt index 724039065b..5dddded1b0 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/AndroidFileController.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/AndroidFileController.kt @@ -503,15 +503,22 @@ internal class AndroidFileController @Inject constructor( override suspend fun writeBytes( uri: String, - onError: (Throwable) -> Unit, block: suspend (Writeable) -> Unit, - ) { - context.openWriteableStream( - uri = uri.toUri(), - onError = onError - )?.let { stream -> - StreamWriteable(stream).use { block(it) } + ): SaveResult { + runCatching { + context.openWriteableStream( + uri = uri.toUri(), + onError = { throw it } + )?.let { stream -> + StreamWriteable(stream).use { block(it) } + } + }.onSuccess { + return SaveResult.Success(null, "") + }.onFailure { + return SaveResult.Error.Exception(it) } + + return SaveResult.Error.Exception(IllegalStateException()) } private fun Context.openWriteableStream( diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/FileWriteable.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/FileWriteable.kt index b6b6be870d..689e01b63c 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/FileWriteable.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/FileWriteable.kt @@ -21,9 +21,7 @@ import ru.tech.imageresizershrinker.core.domain.saving.Writeable import java.io.File import java.io.FileOutputStream -class FileWriteable( - private val file: File -) : Writeable { +internal class FileWriteable(file: File) : Writeable { private val stream = FileOutputStream(file) diff --git a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/StreamWriteable.kt b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/StreamWriteable.kt index 59e41c531e..f8c622c043 100644 --- a/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/StreamWriteable.kt +++ b/core/data/src/main/java/ru/tech/imageresizershrinker/core/data/saving/StreamWriteable.kt @@ -20,7 +20,7 @@ package ru.tech.imageresizershrinker.core.data.saving import ru.tech.imageresizershrinker.core.domain.saving.Writeable import java.io.OutputStream -class StreamWriteable( +internal class StreamWriteable( private val stream: OutputStream ) : Writeable { diff --git a/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/saving/FileController.kt b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/saving/FileController.kt index f7511c1791..0e4a926283 100644 --- a/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/saving/FileController.kt +++ b/core/domain/src/main/kotlin/ru/tech/imageresizershrinker/core/domain/saving/FileController.kt @@ -51,7 +51,6 @@ interface FileController { suspend fun writeBytes( uri: String, - onError: (Throwable) -> Unit = {}, block: suspend (Writeable) -> Unit - ) + ): SaveResult } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt index e474985ad3..ed6a918d1f 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ContextUtils.kt @@ -49,7 +49,6 @@ import ru.tech.imageresizershrinker.core.ui.utils.permission.PermissionUtils.has import ru.tech.imageresizershrinker.core.ui.utils.permission.PermissionUtils.setPermissionsAllowed import java.io.BufferedReader import java.io.InputStreamReader -import java.io.OutputStream import java.util.Locale @@ -320,19 +319,6 @@ object ContextUtils { }.getOrNull() } - fun Context.openWriteableStream( - uri: Uri?, - onError: (Throwable) -> Unit - ): OutputStream? = uri?.let { - runCatching { - contentResolver.openOutputStream(uri, "rw") - }.getOrElse { - runCatching { - contentResolver.openOutputStream(uri, "w") - }.onFailure(onError).getOrNull() - } - } - fun Context.getLanguages(): Map { val languages = mutableListOf("" to getString(R.string.system)).apply { addAll( diff --git a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ParseSaveResult.kt b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ParseSaveResult.kt index 5da7f1334b..7c85090a93 100644 --- a/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ParseSaveResult.kt +++ b/core/ui/src/main/kotlin/ru/tech/imageresizershrinker/core/ui/utils/helper/ParseSaveResult.kt @@ -66,6 +66,33 @@ fun Context.parseSaveResult( } } +fun Context.parseFileSaveResult( + saveResult: SaveResult, + onSuccess: suspend () -> Unit, + toastHostState: ToastHostState, + scope: CoroutineScope +) { + if (saveResult is SaveResult.Error.Exception) { + scope.launch { + toastHostState.showError(this@parseFileSaveResult, saveResult.throwable) + } + } else if (saveResult is SaveResult.Success) { + scope.launch { + onSuccess() + } + scope.launch { + toastHostState.showToast( + getString( + R.string.saved_to_without_filename, + "" + ), + Icons.Rounded.Save + ) + showReview(this@parseFileSaveResult) + } + } +} + fun Activity.parseSaveResults( scope: CoroutineScope, results: List, diff --git a/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/ApngToolsScreen.kt b/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/ApngToolsScreen.kt index 8f481e5c6f..0572a81088 100644 --- a/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/ApngToolsScreen.kt +++ b/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/ApngToolsScreen.kt @@ -53,7 +53,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FolderOff import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme @@ -88,11 +87,10 @@ import ru.tech.imageresizershrinker.core.resources.icons.Jxl import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getFilename -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResults import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen @@ -114,7 +112,6 @@ import ru.tech.imageresizershrinker.core.ui.widget.other.LoadingDialog import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState import ru.tech.imageresizershrinker.core.ui.widget.other.ToastDuration import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji -import ru.tech.imageresizershrinker.core.ui.widget.other.showError import ru.tech.imageresizershrinker.core.ui.widget.preferences.PreferenceItem import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle import ru.tech.imageresizershrinker.feature.apng_tools.presentation.components.ApngParamsSelector @@ -210,37 +207,19 @@ fun ApngToolsScreen( } } - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } val saveApngLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("image/apng"), onResult = { it?.let { uri -> - viewModel.saveApngTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.saveApngTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - ReviewHandler.showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/viewModel/ApngToolsViewModel.kt b/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/viewModel/ApngToolsViewModel.kt index 1bad7e12f0..5c5526352a 100644 --- a/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/viewModel/ApngToolsViewModel.kt +++ b/feature/apng-tools/src/main/java/ru/tech/imageresizershrinker/feature/apng_tools/presentation/viewModel/ApngToolsViewModel.kt @@ -50,7 +50,6 @@ import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.apng_tools.domain.ApngConverter import ru.tech.imageresizershrinker.feature.apng_tools.domain.ApngParams -import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -177,16 +176,17 @@ class ApngToolsViewModel @Inject constructor( } fun saveApngTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(defaultDispatcher) { _isSaving.value = true - kotlin.runCatching { - outputStream?.use { - it.write(apngData) - } - }.exceptionOrNull().let(onComplete) + apngData?.let { byteArray -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(byteArray) } + ).also(onResult).onSuccess(::registerSave) + } _isSaving.value = false apngData = null } diff --git a/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/FileCipherScreen.kt b/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/FileCipherScreen.kt index daeed91083..e162b14ce4 100644 --- a/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/FileCipherScreen.kt +++ b/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/FileCipherScreen.kt @@ -65,7 +65,6 @@ import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.material.icons.rounded.FileDownload import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material.icons.rounded.FolderOpen -import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material.icons.twotone.FileOpen @@ -113,10 +112,9 @@ import ru.tech.imageresizershrinker.core.ui.theme.Green import ru.tech.imageresizershrinker.core.ui.theme.outlineVariant import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getFilename -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream import ru.tech.imageresizershrinker.core.ui.utils.helper.ImageUtils.fileSize -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.showReview import ru.tech.imageresizershrinker.core.ui.utils.helper.isScrollingUp +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedButton import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedFloatingActionButton import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedIconButton @@ -171,37 +169,19 @@ fun FileCipherScreen( else onGoBack() } - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } val saveLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("*/*"), onResult = { it?.let { uri -> - viewModel.saveCryptographyTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.saveCryptographyTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/viewModel/FileCipherViewModel.kt b/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/viewModel/FileCipherViewModel.kt index 03cdc4e540..24c2ae9f71 100644 --- a/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/viewModel/FileCipherViewModel.kt +++ b/feature/cipher/src/main/java/ru/tech/imageresizershrinker/feature/cipher/presentation/viewModel/FileCipherViewModel.kt @@ -27,11 +27,12 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import ru.tech.imageresizershrinker.core.domain.image.ShareProvider +import ru.tech.imageresizershrinker.core.domain.saving.FileController +import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult import ru.tech.imageresizershrinker.core.domain.utils.smartJob import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.cipher.domain.CryptographyManager -import java.io.OutputStream import java.security.InvalidKeyException import javax.inject.Inject @@ -39,6 +40,7 @@ import javax.inject.Inject class FileCipherViewModel @Inject constructor( private val cryptographyManager: CryptographyManager, private val shareProvider: ShareProvider, + private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseViewModel(dispatchersHolder) { @@ -102,16 +104,17 @@ class FileCipherViewModel @Inject constructor( } fun saveCryptographyTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(defaultDispatcher) { _isSaving.value = true - runCatching { - outputStream?.use { - it.write(_byteArray.value) - } - }.exceptionOrNull().let(onComplete) + byteArray?.let { byteArray -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(byteArray) } + ).also(onResult).onSuccess(::registerSave) + } _isSaving.value = false } } diff --git a/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/DocumentScannerScreen.kt b/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/DocumentScannerScreen.kt index 7b0b1028df..b765105ff7 100644 --- a/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/DocumentScannerScreen.kt +++ b/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/DocumentScannerScreen.kt @@ -40,7 +40,6 @@ import androidx.compose.material.icons.outlined.PictureAsPdf import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.rounded.AddPhotoAlternate import androidx.compose.material.icons.rounded.DocumentScanner -import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.twotone.DocumentScanner import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -67,9 +66,8 @@ import ru.tech.imageresizershrinker.core.resources.R import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState import ru.tech.imageresizershrinker.core.ui.shapes.CloverShape import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.showReview import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResults import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberDocumentScanner import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen @@ -127,28 +125,15 @@ fun DocumentScannerScreen( contract = ActivityResultContracts.CreateDocument("application/pdf"), onResult = { it?.let { uri -> - viewModel.savePdfTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.savePdfTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/viewModel/DocumentScannerViewModel.kt b/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/viewModel/DocumentScannerViewModel.kt index 1f337aee62..586832e4c8 100644 --- a/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/viewModel/DocumentScannerViewModel.kt +++ b/feature/document-scanner/src/main/java/ru/tech/imageresizershrinker/feature/document_scanner/presentation/viewModel/DocumentScannerViewModel.kt @@ -45,7 +45,6 @@ import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel import ru.tech.imageresizershrinker.core.ui.utils.helper.ScanResult import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.pdf_tools.domain.PdfManager -import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -75,8 +74,8 @@ class DocumentScannerViewModel @Inject constructor( private suspend fun getPdfUri(): Uri? = if (_pdfUris.value.size > 1 || _pdfUris.value.isEmpty()) { - createPdfUri() - } else _pdfUris.value.firstOrNull() + createPdfUri() + } else _pdfUris.value.firstOrNull() private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving @@ -147,20 +146,18 @@ class DocumentScannerViewModel @Inject constructor( } fun savePdfTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(ioDispatcher) { _isSaving.value = true - getPdfUri()?.let { uri -> - runCatching { - outputStream?.use { - it.write(fileController.readBytes(uri.toString())) - } - }.exceptionOrNull().let(onComplete) - registerSave() + getPdfUri()?.let { pdfUri -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(fileController.readBytes(pdfUri.toString())) } + ).also(onResult).onSuccess(::registerSave) + _isSaving.value = false } - _isSaving.value = false } } diff --git a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddEditMaskSheet.kt b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddEditMaskSheet.kt index f52872dc52..2a25eb4e94 100644 --- a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddEditMaskSheet.kt +++ b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddEditMaskSheet.kt @@ -76,10 +76,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.exifinterface.media.ExifInterface import androidx.lifecycle.viewModelScope -import coil.transform.Transformation import dagger.hilt.android.lifecycle.HiltViewModel import net.engawapg.lib.zoomable.rememberZoomState -import ru.tech.imageresizershrinker.core.data.utils.toCoil import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import ru.tech.imageresizershrinker.core.domain.image.ImageGetter import ru.tech.imageresizershrinker.core.domain.image.ImagePreviewCreator @@ -782,8 +780,4 @@ private class AddMaskSheetViewModel @Inject constructor( transformations = filters.map { filterProvider.filterToTransformation(it) } ) - fun filterToTransformation( - uiFilter: UiFilter<*> - ): Transformation = filterProvider.filterToTransformation(uiFilter).toCoil() - } \ No newline at end of file diff --git a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddFiltersSheet.kt b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddFiltersSheet.kt index 551e684b1d..50660581bd 100644 --- a/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddFiltersSheet.kt +++ b/feature/filters/src/main/java/ru/tech/imageresizershrinker/feature/filters/presentation/components/AddFiltersSheet.kt @@ -67,7 +67,6 @@ import androidx.compose.material.icons.rounded.FilterHdr import androidx.compose.material.icons.rounded.FormatColorFill import androidx.compose.material.icons.rounded.LensBlur import androidx.compose.material.icons.rounded.Light -import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material.icons.rounded.Speed @@ -108,12 +107,10 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.exifinterface.media.ExifInterface -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import coil.transform.Transformation import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import ru.tech.imageresizershrinker.core.domain.image.ImageCompressor import ru.tech.imageresizershrinker.core.domain.image.ImageTransformer @@ -136,10 +133,12 @@ import ru.tech.imageresizershrinker.core.filters.presentation.utils.getTemplateF import ru.tech.imageresizershrinker.core.resources.R import ru.tech.imageresizershrinker.core.resources.icons.BookmarkOff import ru.tech.imageresizershrinker.core.resources.icons.Cube +import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getStringLocalized import ru.tech.imageresizershrinker.core.ui.utils.helper.ImageUtils.safeAspectRatio import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.toCoil import ru.tech.imageresizershrinker.core.ui.utils.state.update @@ -153,7 +152,6 @@ import ru.tech.imageresizershrinker.core.ui.widget.modifier.shimmer import ru.tech.imageresizershrinker.core.ui.widget.other.EnhancedTopAppBar import ru.tech.imageresizershrinker.core.ui.widget.other.EnhancedTopAppBarType import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState -import ru.tech.imageresizershrinker.core.ui.widget.other.showError import ru.tech.imageresizershrinker.core.ui.widget.sheets.SimpleDragHandle import ru.tech.imageresizershrinker.core.ui.widget.sheets.SimpleSheet import ru.tech.imageresizershrinker.core.ui.widget.sheets.SimpleSheetDefaults @@ -178,7 +176,7 @@ private class AddFiltersSheetViewModel @Inject constructor( private val fileController: FileController, private val imageCompressor: ImageCompressor, private val dispatchersHolder: DispatchersHolder -) : ViewModel(), DispatchersHolder by dispatchersHolder { +) : BaseViewModel(dispatchersHolder) { private val _previewData: MutableState>?> = mutableStateOf(null) val previewData by _previewData @@ -286,32 +284,25 @@ private class AddFiltersSheetViewModel @Inject constructor( fun saveContentTo( content: String, fileUri: Uri, - onComplete: (Throwable?) -> Unit + onResult: (SaveResult) -> Unit ) { viewModelScope.launch(ioDispatcher) { - runCatching { - fileController.writeBytes( - fileUri.toString(), - onError = { - onComplete(it) - return@writeBytes - } - ) { - it.writeBytes(content.toByteArray()) - } - }.exceptionOrNull().let(onComplete) + fileController.writeBytes( + uri = fileUri.toString(), + block = { it.writeBytes(content.toByteArray()) } + ).also(onResult).onSuccess(::registerSave) } } fun shareContent( content: String, - templateFilter: TemplateFilter, + filename: String, onComplete: () -> Unit ) { viewModelScope.launch { shareProvider.shareData( writeData = { it.writeBytes(content.toByteArray()) }, - filename = createTemplateFilename(templateFilter), + filename = filename, onComplete = onComplete ) } @@ -658,28 +649,15 @@ fun AddFiltersSheet( viewModel.saveContentTo( content = content, fileUri = fileUri - ) { throwable -> - if (throwable != null) { - scope.launch { - toastHostState.showError( - context, - throwable - ) - } - } else { - scope.launch { + ) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } }, onRequestTemplateFilename = { @@ -690,7 +668,9 @@ fun AddFiltersSheet( onShareFile = { content -> viewModel.shareContent( content = content, - templateFilter = templateFilter, + filename = viewModel.createTemplateFilename( + templateFilter + ), onComplete = showConfetti ) } diff --git a/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/GifToolsScreen.kt b/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/GifToolsScreen.kt index e85c2f7241..0e013e7ecb 100644 --- a/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/GifToolsScreen.kt +++ b/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/GifToolsScreen.kt @@ -54,7 +54,6 @@ import androidx.compose.material.icons.outlined.FolderOff import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Gif -import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme @@ -87,11 +86,10 @@ import ru.tech.imageresizershrinker.core.resources.icons.Jxl import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getFilename -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResults import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen @@ -113,7 +111,6 @@ import ru.tech.imageresizershrinker.core.ui.widget.other.LoadingDialog import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState import ru.tech.imageresizershrinker.core.ui.widget.other.ToastDuration import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji -import ru.tech.imageresizershrinker.core.ui.widget.other.showError import ru.tech.imageresizershrinker.core.ui.widget.preferences.PreferenceItem import ru.tech.imageresizershrinker.core.ui.widget.text.TopAppBarTitle import ru.tech.imageresizershrinker.feature.gif_tools.presentation.components.GifParamsSelector @@ -209,37 +206,19 @@ fun GifToolsScreen( } } - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } val saveGifLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("image/gif"), onResult = { it?.let { uri -> - viewModel.saveGifTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.saveGifTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - ReviewHandler.showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/viewModel/GifToolsViewModel.kt b/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/viewModel/GifToolsViewModel.kt index 3ab7e4ab2d..a80e0f275b 100644 --- a/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/viewModel/GifToolsViewModel.kt +++ b/feature/gif-tools/src/main/java/ru/tech/imageresizershrinker/feature/gif_tools/presentation/viewModel/GifToolsViewModel.kt @@ -50,7 +50,6 @@ import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.gif_tools.domain.GifConverter import ru.tech.imageresizershrinker.feature.gif_tools.domain.GifParams -import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -181,16 +180,17 @@ class GifToolsViewModel @Inject constructor( } fun saveGifTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(defaultDispatcher) { _isSaving.value = true - runCatching { - outputStream?.use { - it.write(gifData) - } - }.exceptionOrNull().let(onComplete) + gifData?.let { byteArray -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(byteArray) } + ).also(onResult).onSuccess(::registerSave) + } _isSaving.value = false gifData = null } diff --git a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/PdfToolsScreen.kt b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/PdfToolsScreen.kt index 92bf8e712e..56ce3ff653 100644 --- a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/PdfToolsScreen.kt +++ b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/PdfToolsScreen.kt @@ -115,10 +115,9 @@ import ru.tech.imageresizershrinker.core.resources.R import ru.tech.imageresizershrinker.core.ui.utils.animation.fancySlideTransition import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.getFilename -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.showReview import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.helper.parseSaveResults import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen @@ -201,37 +200,19 @@ fun PdfToolsScreen( } } - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } val savePdfLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("application/pdf"), onResult = { it?.let { uri -> - viewModel.savePdfTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.savePdfTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/viewModel/PdfToolsViewModel.kt b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/viewModel/PdfToolsViewModel.kt index 9bf6ac4381..44240c6a0d 100644 --- a/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/viewModel/PdfToolsViewModel.kt +++ b/feature/pdf-tools/src/main/java/ru/tech/imageresizershrinker/feature/pdf_tools/presentation/viewModel/PdfToolsViewModel.kt @@ -44,7 +44,6 @@ import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.pdf_tools.domain.PdfManager import ru.tech.imageresizershrinker.feature.pdf_tools.presentation.components.PdfToImageState -import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -100,16 +99,17 @@ class PdfToolsViewModel @Inject constructor( } fun savePdfTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(ioDispatcher) { _isSaving.value = true - runCatching { - outputStream?.use { - it.write(_byteArray.value) - } - }.exceptionOrNull().let(onComplete) + _byteArray.value?.let { byteArray -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(byteArray) } + ).also(onResult).onSuccess(::registerSave) + } _isSaving.value = false } } diff --git a/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/components/SettingItem.kt b/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/components/SettingItem.kt index d1990a0020..bfd269690d 100644 --- a/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/components/SettingItem.kt +++ b/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/components/SettingItem.kt @@ -47,7 +47,7 @@ import ru.tech.imageresizershrinker.core.ui.theme.mixedContainer import ru.tech.imageresizershrinker.core.ui.theme.onMixedContainer import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalContainerShape import ru.tech.imageresizershrinker.core.ui.utils.provider.ProvideContainerDefaults import ru.tech.imageresizershrinker.core.ui.widget.modifier.ContainerShapeDefaults @@ -140,29 +140,20 @@ internal fun SettingItem( } Setting.Backup -> { - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } BackupSettingItem( createBackupFilename = viewModel::createBackupFilename, createBackup = { uri -> viewModel.createBackup( - outputStream = context.openWriteableStream(uri, writeDenied), - onSuccess = { - scope.launch { - confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - } + uri = uri, + onResult = { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { + confettiHostState.showConfetti() + }, + toastHostState = toastHostState, + scope = scope + ) } ) } diff --git a/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/viewModel/SettingsViewModel.kt b/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/viewModel/SettingsViewModel.kt index 9a07e20260..c8324efe54 100644 --- a/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/viewModel/SettingsViewModel.kt +++ b/feature/settings/src/main/java/ru/tech/imageresizershrinker/feature/settings/presentation/viewModel/SettingsViewModel.kt @@ -36,6 +36,7 @@ import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import ru.tech.imageresizershrinker.core.domain.image.ImageGetter import ru.tech.imageresizershrinker.core.domain.image.model.ImageScaleMode import ru.tech.imageresizershrinker.core.domain.saving.FileController +import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult import ru.tech.imageresizershrinker.core.settings.domain.SettingsManager import ru.tech.imageresizershrinker.core.settings.domain.model.ColorHarmonizer import ru.tech.imageresizershrinker.core.settings.domain.model.CopyToClipboardMode @@ -45,7 +46,6 @@ import ru.tech.imageresizershrinker.core.settings.domain.model.SettingsState import ru.tech.imageresizershrinker.core.settings.domain.model.SwitchType import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel import ru.tech.imageresizershrinker.core.ui.utils.navigation.Screen -import java.io.OutputStream import javax.inject.Inject @HiltViewModel @@ -222,14 +222,14 @@ class SettingsViewModel @Inject constructor( } fun createBackup( - outputStream: OutputStream?, - onSuccess: () -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { viewModelScope.launch(ioDispatcher) { - outputStream?.use { - it.write(settingsManager.createBackupFile()) - } - onSuccess() + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(settingsManager.createBackupFile()) } + ).also(onResult) } } diff --git a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/ZipScreen.kt b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/ZipScreen.kt index 8bed7cc446..47d8136ab9 100644 --- a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/ZipScreen.kt +++ b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/ZipScreen.kt @@ -41,7 +41,6 @@ import androidx.compose.material.icons.outlined.FolderOff import androidx.compose.material.icons.rounded.CheckCircle import androidx.compose.material.icons.rounded.FileDownload import androidx.compose.material.icons.rounded.FileOpen -import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.twotone.FileOpen import androidx.compose.material3.Icon @@ -74,9 +73,8 @@ import ru.tech.imageresizershrinker.core.ui.shapes.CloverShape import ru.tech.imageresizershrinker.core.ui.theme.Green import ru.tech.imageresizershrinker.core.ui.theme.outlineVariant import ru.tech.imageresizershrinker.core.ui.utils.confetti.LocalConfettiHostState -import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.openWriteableStream -import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.showReview import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState +import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen import ru.tech.imageresizershrinker.core.ui.widget.buttons.BottomButtonsBlock import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedButton @@ -124,37 +122,19 @@ fun ZipScreen( } else onGoBack() } - val writeDenied: (Throwable) -> Unit = { - scope.launch { - toastHostState.showError(context, it) - } - } val saveLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("application/zip"), onResult = { it?.let { uri -> - viewModel.saveResultTo( - outputStream = context.openWriteableStream(uri, writeDenied) - ) { t -> - if (t != null) { - scope.launch { - toastHostState.showError(context, t) - } - } else { - scope.launch { + viewModel.saveResultTo(uri) { result -> + context.parseFileSaveResult( + saveResult = result, + onSuccess = { confettiHostState.showConfetti() - } - scope.launch { - toastHostState.showToast( - context.getString( - R.string.saved_to_without_filename, - "" - ), - Icons.Rounded.Save - ) - showReview(context) - } - } + }, + toastHostState = toastHostState, + scope = scope + ) } } } diff --git a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/viewModel/ZipViewModel.kt b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/viewModel/ZipViewModel.kt index 7d95fb08b1..1e0c7e1975 100644 --- a/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/viewModel/ZipViewModel.kt +++ b/feature/zip/src/main/java/ru/tech/imageresizershrinker/feature/zip/presentation/viewModel/ZipViewModel.kt @@ -28,17 +28,19 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder import ru.tech.imageresizershrinker.core.domain.image.ShareProvider +import ru.tech.imageresizershrinker.core.domain.saving.FileController +import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult import ru.tech.imageresizershrinker.core.domain.utils.smartJob import ru.tech.imageresizershrinker.core.ui.utils.BaseViewModel import ru.tech.imageresizershrinker.core.ui.utils.state.update import ru.tech.imageresizershrinker.feature.zip.domain.ZipManager -import java.io.OutputStream import javax.inject.Inject @HiltViewModel class ZipViewModel @Inject constructor( private val zipManager: ZipManager, private val shareProvider: ShareProvider, + private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseViewModel(dispatchersHolder) { @@ -94,16 +96,17 @@ class ZipViewModel @Inject constructor( } fun saveResultTo( - outputStream: OutputStream?, - onComplete: (Throwable?) -> Unit + uri: Uri, + onResult: (SaveResult) -> Unit ) { savingJob = viewModelScope.launch(defaultDispatcher) { _isSaving.value = true - runCatching { - outputStream?.use { - it.write(_byteArray.value) - } - }.exceptionOrNull().let(onComplete) + _byteArray.value?.let { byteArray -> + fileController.writeBytes( + uri = uri.toString(), + block = { it.writeBytes(byteArray) } + ).also(onResult).onSuccess(::registerSave) + } _isSaving.value = false } }