Applying Suggestions

This commit is contained in:
Roshan Varughese 2024-08-27 13:57:59 +12:00
parent f1574b12cd
commit fa56c0bf4c
2 changed files with 147 additions and 279 deletions

View File

@ -7,7 +7,9 @@ import android.net.Uri
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@ -15,6 +17,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
@ -23,11 +27,14 @@ import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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
import androidx.compose.ui.platform.LocalUriHandler
@ -37,7 +44,6 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import com.hippo.unifile.UniFile
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
import eu.kanade.presentation.more.settings.screen.data.LibraryListScreen
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
@ -50,6 +56,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.displayablePath
@ -58,8 +66,10 @@ import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
@ -317,16 +327,149 @@ object SettingsDataScreen : SearchableSettings {
@Composable
private fun getExportGroup(): Preference.PreferenceGroup {
val navigator = LocalNavigator.currentOrThrow
var showDialog by remember { mutableStateOf(false) }
var titleSelected by remember { mutableStateOf(true) }
var authorSelected by remember { mutableStateOf(true) }
var artistSelected by remember { mutableStateOf(true) }
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val getFavorites: GetFavorites = Injekt.get()
val favoritesFlow = remember { flow { emit(getFavorites.await()) } }
val favoritesState by favoritesFlow.collectAsState(emptyList())
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 = { newTitleSelected, newAuthorSelected, newArtistSelected ->
titleSelected = newTitleSelected
authorSelected = newAuthorSelected
artistSelected = newArtistSelected
saveFileLauncher.launch("library_list.csv")
},
isTitleSelected = titleSelected,
isAuthorSelected = authorSelected,
isArtistSelected = artistSelected,
)
}
return Preference.PreferenceGroup(
title = "Export",
preferenceItems = persistentListOf(
Preference.PreferenceItem.TextPreference(
title = LibraryListScreen.TITLE,
onClick = { navigator.push(LibraryListScreen()) },
title = "Library List",
onClick = { showDialog = true },
),
),
)
}
private fun escapeCsvField(field: String): String {
return field.replace("\"", "\"\"").replace("\r\n", "\n").replace("\r", "\n")
}
@Composable
private 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))
}
},
)
}
}

View File

@ -1,275 +0,0 @@
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
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.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
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
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 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
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun BaseMangaListItem(
manga: Manga,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.height(56.dp)
.padding(horizontal = MaterialTheme.padding.medium),
verticalAlignment = Alignment.CenterVertically,
) {
MangaCover.Square(
modifier = Modifier
.padding(vertical = MaterialTheme.padding.small)
.fillMaxHeight(),
data = manga,
)
Box(modifier = Modifier.weight(1f)) {
Text(
text = manga.title,
modifier = Modifier
.padding(start = MaterialTheme.padding.medium),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
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
val navigator = LocalNavigator.currentOrThrow
val getFavorites: GetFavorites = Injekt.get()
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(
title = TITLE,
navigateUp = navigator::pop,
actions = {
AppBarActions(
persistentListOf(
AppBar.Action(
title = stringResource(MR.strings.action_copy_to_clipboard),
icon = Icons.Default.Save,
onClick = { showDialog = true },
),
),
)
},
)
},
) { contentPadding ->
if (favoritesState.isEmpty()) {
EmptyScreen(
stringRes = MR.strings.empty_screen,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
}
LazyColumn(
modifier = Modifier
.padding(contentPadding)
.padding(8.dp),
) {
items(favoritesState) { manga ->
BaseMangaListItem(
manga = manga,
)
}
}
}
}
}
@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))
}
},
)
}