1414 */
1515package org.fairscan.app.ui.screens.export
1616
17+ import android.content.ContentValues
1718import android.content.Context
1819import android.media.MediaScannerConnection
1920import android.net.Uri
21+ import android.os.Build
22+ import android.os.Environment
23+ import android.provider.MediaStore
24+ import androidx.annotation.RequiresApi
2025import androidx.core.net.toUri
2126import androidx.documentfile.provider.DocumentFile
2227import androidx.lifecycle.ViewModel
@@ -39,6 +44,7 @@ import org.fairscan.app.data.ImageRepository
3944import org.fairscan.app.ui.screens.settings.ExportFormat
4045import java.io.File
4146import java.io.FileInputStream
47+ import java.io.IOException
4248import kotlin.coroutines.resume
4349import kotlin.coroutines.suspendCoroutine
4450
@@ -184,11 +190,21 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
184190
185191 for (file in result.files) {
186192 val saved = if (exportDir == null ) {
187- val out = fileManager.copyToExternalDir(file)
188- filesForMediaScan.add(out )
189- SavedItem (out .toUri(), out .name, exportFormat)
193+ // No export dir defined -> save to Downloads
194+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
195+ // Android 10+: use MediaStore API
196+ val uri = saveViaMediaStore(context, file, exportFormat)
197+ SavedItem (uri, file.name, exportFormat)
198+ } else {
199+ // Android 8 and 9: use File API
200+ // (MediaStore doesn't allow to choose Downloads for Android<10)
201+ val out = fileManager.copyToExternalDir(file)
202+ filesForMediaScan.add(out )
203+ SavedItem (out .toUri(), out .name, exportFormat)
204+ }
190205 } else {
191- val safFile = copyViaSaf(context, file, exportDir, exportFormat)
206+ // Use Storage Access Framework to save to the chosen directory
207+ val safFile = saveViaSaf(context, file, exportDir, exportFormat)
192208 SavedItem (safFile.uri, safFile.name ? : file.name, exportFormat)
193209 }
194210 savedItems + = saved
@@ -221,7 +237,34 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
221237 }
222238 }
223239
224- private fun copyViaSaf (
240+ @RequiresApi(Build .VERSION_CODES .Q )
241+ private fun saveViaMediaStore (
242+ context : Context ,
243+ source : File ,
244+ format : ExportFormat
245+ ): Uri {
246+ val resolver = context.contentResolver
247+
248+ val values = ContentValues ().apply {
249+ put(MediaStore .MediaColumns .DISPLAY_NAME , source.name)
250+ put(MediaStore .MediaColumns .MIME_TYPE , format.mimeType)
251+ put(MediaStore .MediaColumns .RELATIVE_PATH , Environment .DIRECTORY_DOWNLOADS )
252+ }
253+
254+ val collection = MediaStore .Downloads .EXTERNAL_CONTENT_URI
255+ val uri = resolver.insert(collection, values)
256+ ? : throw IOException (" Failed to create MediaStore entry" )
257+
258+ resolver.openOutputStream(uri)?.use { out ->
259+ source.inputStream().use { input ->
260+ input.copyTo(out )
261+ }
262+ } ? : throw IOException (" Failed to open output stream" )
263+
264+ return uri
265+ }
266+
267+ private fun saveViaSaf (
225268 context : Context ,
226269 source : File ,
227270 exportDirUri : Uri ,
0 commit comments