Skip to content

Commit 3458e45

Browse files
Add user-selectable CSV export location feature (#168)
* Initial plan * Implement CSV export location chooser feature Co-authored-by: yogeshpaliyal <[email protected]> * feat: implement option for users to choose CSV export location --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: yogeshpaliyal <[email protected]> Co-authored-by: Yogesh Choudhary Paliyal <[email protected]>
1 parent ec66367 commit 3458e45

File tree

5 files changed

+36
-27
lines changed

5 files changed

+36
-27
lines changed

.github/copilot-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
- Before commiting run `./gradlew :app:formatKotlin` and then `./gradlew :app:lintKotlin` to ensure code style and lint checks pass.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.yogeshpaliyal.deepr.backup
22

3+
import android.net.Uri
34
import com.yogeshpaliyal.deepr.util.RequestResult
45

56
interface ExportRepository {
6-
suspend fun exportToCsv(): RequestResult<String>
7+
suspend fun exportToCsv(uri: Uri? = null): RequestResult<String>
78
}

app/src/main/java/com/yogeshpaliyal/deepr/backup/ExportRepositoryImpl.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.yogeshpaliyal.deepr.backup
22

33
import android.content.ContentValues
44
import android.content.Context
5+
import android.net.Uri
56
import android.os.Build
67
import android.os.Environment
78
import android.provider.MediaStore
@@ -23,7 +24,7 @@ class ExportRepositoryImpl(
2324
private val context: Context,
2425
private val deeprQueries: DeeprQueries,
2526
) : ExportRepository {
26-
override suspend fun exportToCsv(): RequestResult<String> {
27+
override suspend fun exportToCsv(uri: Uri?): RequestResult<String> {
2728
val count = deeprQueries.countDeepr().executeAsOne()
2829
if (count == 0L) {
2930
return RequestResult.Error(context.getString(R.string.no_data_to_export))
@@ -37,6 +38,19 @@ class ExportRepositoryImpl(
3738
val fileName = "deepr_export_$timeStamp.csv"
3839

3940
return withContext(Dispatchers.IO) {
41+
// If URI is provided, export to that location
42+
if (uri != null) {
43+
return@withContext try {
44+
context.contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
45+
writeCsvData(outputStream, dataToExportInCsvFormat)
46+
}
47+
RequestResult.Success(context.getString(R.string.export_success, uri.toString()))
48+
} catch (e: Exception) {
49+
RequestResult.Error(context.getString(R.string.export_failed))
50+
}
51+
}
52+
53+
// Default behavior: export to Downloads/Deepr folder
4054
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
4155
val contentValues =
4256
ContentValues().apply {
@@ -49,11 +63,11 @@ class ExportRepositoryImpl(
4963
}
5064

5165
val resolver = context.contentResolver
52-
val uri =
66+
val defaultUri =
5367
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
5468

55-
if (uri != null) {
56-
resolver.openOutputStream(uri)?.use { outputStream ->
69+
if (defaultUri != null) {
70+
resolver.openOutputStream(defaultUri)?.use { outputStream ->
5771
writeCsvData(outputStream, dataToExportInCsvFormat)
5872
}
5973
RequestResult.Success(context.getString(R.string.export_success, "${Environment.DIRECTORY_DOWNLOADS}/Deepr/$fileName"))

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.yogeshpaliyal.deepr.ui.screens
22

3-
import android.Manifest
43
import android.content.Intent
5-
import android.os.Build
64
import android.widget.Toast
75
import androidx.activity.compose.rememberLauncherForActivityResult
86
import androidx.activity.result.contract.ActivityResultContracts
@@ -52,8 +50,6 @@ import androidx.compose.ui.unit.LayoutDirection
5250
import androidx.compose.ui.unit.dp
5351
import androidx.lifecycle.compose.collectAsStateWithLifecycle
5452
import com.google.accompanist.permissions.ExperimentalPermissionsApi
55-
import com.google.accompanist.permissions.isGranted
56-
import com.google.accompanist.permissions.rememberPermissionState
5753
import com.yogeshpaliyal.deepr.BuildConfig
5854
import com.yogeshpaliyal.deepr.MainActivity
5955
import com.yogeshpaliyal.deepr.R
@@ -88,7 +84,6 @@ fun SettingsScreen(
8884
viewModel: AccountViewModel = koinViewModel(),
8985
) {
9086
val context = LocalContext.current
91-
val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE)
9287
val launcherActivityPickResult =
9388
rememberLauncherForActivityResult(
9489
contract = ActivityResultContracts.OpenDocument(),
@@ -98,6 +93,16 @@ fun SettingsScreen(
9893
}
9994
}
10095

96+
// Launcher for picking CSV export location
97+
val csvExportLauncher =
98+
rememberLauncherForActivityResult(
99+
contract = ActivityResultContracts.CreateDocument("text/csv"),
100+
) { uri ->
101+
uri?.let {
102+
viewModel.exportCsvData(it)
103+
}
104+
}
105+
101106
// Collect the shortcut icon preference state
102107
val useLinkBasedIcons by viewModel.useLinkBasedIcons.collectAsStateWithLifecycle()
103108

@@ -127,12 +132,6 @@ fun SettingsScreen(
127132
}
128133
}
129134

130-
LaunchedEffect(storagePermissionState.status) {
131-
if (storagePermissionState.status.isGranted) {
132-
viewModel.exportCsvData()
133-
}
134-
}
135-
136135
LaunchedEffect(true) {
137136
viewModel.exportResultFlow.collectLatest { message ->
138137
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
@@ -221,15 +220,8 @@ fun SettingsScreen(
221220
title = stringResource(R.string.export_deeplinks),
222221
description = stringResource(R.string.export_deeplinks_description),
223222
onClick = {
224-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
225-
viewModel.exportCsvData()
226-
} else {
227-
if (storagePermissionState.status.isGranted) {
228-
viewModel.exportCsvData()
229-
} else {
230-
storagePermissionState.launchPermissionRequest()
231-
}
232-
}
223+
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US).format(java.util.Date())
224+
csvExportLauncher.launch("deepr_export_$timeStamp.csv")
233225
},
234226
)
235227
}

app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,9 @@ class AccountViewModel(
326326
}
327327
}
328328

329-
fun exportCsvData() {
329+
fun exportCsvData(uri: Uri? = null) {
330330
viewModelScope.launch(Dispatchers.IO) {
331-
val result = exportRepository.exportToCsv()
331+
val result = exportRepository.exportToCsv(uri)
332332
when (result) {
333333
is RequestResult.Success -> {
334334
exportResultChannel.send("Export completed: ${result.data}")

0 commit comments

Comments
 (0)