Save as CSV

This commit is contained in:
Roshan Varughese 2024-08-26 21:41:06 +12:00
parent b9b955befd
commit 5f505cf028

View file

@ -1,6 +1,9 @@
package eu.kanade.presentation.more.settings.screen.data
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
@ -8,13 +11,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -26,13 +34,14 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
@ -70,12 +79,16 @@ fun BaseMangaListItem(
}
}
class LibraryDebugListScreen : Screen() {
class LibraryListScreen : Screen() {
companion object {
const val TITLE = "Library List"
}
private fun escapeCsvField(field: String): String {
return field.replace("\"", "\"\"").replace("\r\n", "\n").replace("\r", "\n")
}
@Composable
override fun Content() {
val context = LocalContext.current
@ -85,6 +98,60 @@ class LibraryDebugListScreen : Screen() {
val favoritesFlow = remember { flow { emit(getFavorites.await()) } }
val favoritesState by favoritesFlow.collectAsState(emptyList())
var showDialog by remember { mutableStateOf(false) }
// Declare the selection states
var titleSelected by remember { mutableStateOf(true) }
var authorSelected by remember { mutableStateOf(true) }
var artistSelected by remember { mutableStateOf(true) }
val coroutineScope = rememberCoroutineScope()
// Setup the activity result launcher to handle the file save
val saveFileLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("text/csv"),
) { uri ->
uri?.let {
coroutineScope.launch {
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
// Prepare CSV data
val csvData = buildString {
favoritesState.forEach { manga ->
val title = if (titleSelected) escapeCsvField(manga.title) else ""
val author = if (authorSelected) escapeCsvField(manga.author ?: "") else ""
val artist = if (artistSelected) escapeCsvField(manga.artist ?: "") else ""
val row = listOf(title, author, artist).filter {
it.isNotEmpty()
}.joinToString(",") { "\"$it\"" }
appendLine(row)
}
}
// Write CSV data to output stream
outputStream.write(csvData.toByteArray())
outputStream.flush()
}
}
}
}
if (showDialog) {
ColumnSelectionDialog(
onDismissRequest = { showDialog = false },
onConfirm = { selectedTitle, selectedAuthor, selectedArtist ->
titleSelected = selectedTitle
authorSelected = selectedAuthor
artistSelected = selectedArtist
// Launch the save document intent
saveFileLauncher.launch("manga_list.csv")
showDialog = false
},
isTitleSelected = titleSelected,
isAuthorSelected = authorSelected,
isArtistSelected = artistSelected,
)
}
Scaffold(
topBar = {
AppBar(
@ -95,15 +162,8 @@ class LibraryDebugListScreen : Screen() {
persistentListOf(
AppBar.Action(
title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.ContentCopy,
onClick = {
val csvData = favoritesState.joinToString("\n") { manga ->
val author = manga.author ?: ""
val artist = manga.artist ?: ""
"${manga.title}, $author, $artist"
}
context.copyToClipboard(TITLE, csvData)
},
icon = Icons.Default.Save,
onClick = { showDialog = true },
),
),
)
@ -134,3 +194,82 @@ class LibraryDebugListScreen : Screen() {
}
}
}
@Composable
fun ColumnSelectionDialog(
onDismissRequest: () -> Unit,
onConfirm: (Boolean, Boolean, Boolean) -> Unit,
isTitleSelected: Boolean,
isAuthorSelected: Boolean,
isArtistSelected: Boolean,
) {
var titleSelected by remember { mutableStateOf(isTitleSelected) }
var authorSelected by remember { mutableStateOf(isAuthorSelected) }
var artistSelected by remember { mutableStateOf(isArtistSelected) }
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = "Select Fields")
},
text = {
Column {
// Title checkbox
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = titleSelected,
onCheckedChange = { checked ->
titleSelected = checked
if (!checked) {
authorSelected = false
artistSelected = false
}
},
)
Text(text = stringResource(MR.strings.title))
}
// Author checkbox, disabled if Title is not selected
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = authorSelected,
onCheckedChange = { authorSelected = it },
enabled = titleSelected,
)
Text(text = "Author")
}
// Artist checkbox, disabled if Title is not selected
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = artistSelected,
onCheckedChange = { artistSelected = it },
enabled = titleSelected,
)
Text(text = "Artist")
}
}
},
confirmButton = {
TextButton(
onClick = {
onConfirm(titleSelected, authorSelected, artistSelected)
onDismissRequest()
},
) {
Text(text = "Save")
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
)
}