This commit is contained in:
Roshan Varughese 2024-12-25 10:54:11 +00:00 committed by GitHub
commit cbc5e1900e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 207 additions and 0 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
@ -45,10 +52,13 @@ import eu.kanade.presentation.util.relativeTimeSpanString
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.export.LibraryExporter
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
@ -57,8 +67,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
@ -95,6 +107,7 @@ object SettingsDataScreen : SearchableSettings {
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
getDataGroup(),
getExportGroup(),
)
}
@ -312,4 +325,137 @@ object SettingsDataScreen : SearchableSettings {
),
)
}
@Composable
private fun getExportGroup(): Preference.PreferenceGroup {
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 libraryExporter = LibraryExporter()
val saveFileLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("text/csv"),
) { uri ->
uri?.let {
libraryExporter.exportToCsv(
context,
it,
favoritesState,
LibraryExporter.ExportOptions(titleSelected, authorSelected, artistSelected),
coroutineScope,
) {
context.toast(MR.strings.library_exported)
}
}
}
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 = stringResource(MR.strings.export),
preferenceItems = persistentListOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.library_list),
onClick = { showDialog = true },
),
),
)
}
@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 = stringResource(MR.strings.migration_dialog_what_to_include))
},
text = {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = titleSelected,
onCheckedChange = { checked ->
titleSelected = checked
if (!checked) {
authorSelected = false
artistSelected = false
}
},
)
Text(text = stringResource(MR.strings.title))
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = authorSelected,
onCheckedChange = { authorSelected = it },
enabled = titleSelected,
)
Text(text = stringResource(MR.strings.author))
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = artistSelected,
onCheckedChange = { artistSelected = it },
enabled = titleSelected,
)
Text(text = stringResource(MR.strings.artist))
}
}
},
confirmButton = {
TextButton(
onClick = {
onConfirm(titleSelected, authorSelected, artistSelected)
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_save))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
)
}
}

View File

@ -0,0 +1,55 @@
package eu.kanade.tachiyomi.data.export
import android.content.Context
import android.net.Uri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import tachiyomi.domain.manga.model.Manga
class LibraryExporter {
data class ExportOptions(
val includeTitle: Boolean,
val includeAuthor: Boolean,
val includeArtist: Boolean,
)
fun exportToCsv(
context: Context,
uri: Uri,
favorites: List<Manga>,
options: ExportOptions,
coroutineScope: CoroutineScope,
onExportComplete: () -> Unit,
) {
coroutineScope.launch {
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
val csvData = generateCsvData(favorites, options)
outputStream.write(csvData.toByteArray())
outputStream.flush()
onExportComplete()
}
}
}
private fun generateCsvData(favorites: List<Manga>, options: ExportOptions): String {
val stringBuilder = StringBuilder()
favorites.forEach { manga ->
val row = mutableListOf<String>()
if (options.includeTitle) row.add(escapeCsvField(manga.title))
if (options.includeAuthor) row.add(escapeCsvField(manga.author ?: ""))
if (options.includeArtist) row.add(escapeCsvField(manga.artist ?: ""))
if (row.isNotEmpty()) {
stringBuilder.appendLine(row.joinToString(",") { "\"$it\"" })
}
}
return stringBuilder.toString()
}
private fun escapeCsvField(field: String): String {
return field
.replace("\"", "\"\"")
.replace("\r\n", "\n")
.replace("\r", "\n")
}
}

View File

@ -573,6 +573,10 @@
<string name="cache_deleted">Cache cleared, %1$d files deleted</string>
<string name="cache_delete_error">Error occurred while clearing</string>
<string name="pref_auto_clear_chapter_cache">Clear chapter cache on app launch</string>
<string name="export">Export</string>
<string name="library_list">Library List</string>
<string name="library_exported">Library Exported</string>
<!-- Sync section -->
<string name="syncing_library">Syncing library</string>
@ -687,6 +691,8 @@
<string name="ongoing">Ongoing</string>
<string name="unknown">Unknown</string>
<string name="unknown_author">Unknown author</string>
<string name="author">Author</string>
<string name="artist">Artist</string>
<!-- reserved for #6163 -->
<string name="unknown_status">Unknown status</string>
<string name="licensed">Licensed</string>