Convert Batch Add to Compose + Voyager
This commit is contained in:
parent
0142e0f771
commit
c14b7879a4
@ -1,168 +1,16 @@
|
|||||||
package exh.ui.batchadd
|
package exh.ui.batchadd
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import androidx.compose.runtime.Composable
|
||||||
import android.view.View
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import android.widget.TextView
|
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.EhFragmentBatchAddBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.buffer
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import reactivecircus.flowbinding.android.view.clicks
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch add screen
|
* Batch add screen
|
||||||
*/
|
*/
|
||||||
class BatchAddController : NucleusController<EhFragmentBatchAddBinding, BatchAddPresenter>() {
|
class BatchAddController : BasicFullComposeController() {
|
||||||
override fun getTitle() = activity!!.getString(R.string.batch_add)
|
|
||||||
|
|
||||||
override fun createPresenter() = BatchAddPresenter()
|
@Composable
|
||||||
|
override fun ComposeContent() {
|
||||||
override fun createBinding(inflater: LayoutInflater) = EhFragmentBatchAddBinding.inflate(inflater)
|
Navigator(screen = BatchAddScreen())
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.btnAddGalleries.clicks()
|
|
||||||
.onEach {
|
|
||||||
addGalleries(binding.galleriesBox.text.toString())
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
|
|
||||||
binding.progressDismissBtn.clicks()
|
|
||||||
.onEach {
|
|
||||||
presenter.currentlyAddingFlow.value = BatchAddPresenter.STATE_PROGRESS_TO_INPUT
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
|
|
||||||
binding.scrollView.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
presenter.currentlyAddingFlow
|
|
||||||
.buffer(capacity = 0, onBufferOverflow = BufferOverflow.SUSPEND)
|
|
||||||
.mapLatest { event ->
|
|
||||||
when (event) {
|
|
||||||
BatchAddPresenter.STATE_INPUT_TO_PROGRESS -> coroutineScope {
|
|
||||||
showProgress(binding)
|
|
||||||
presenter.progressFlow
|
|
||||||
.buffer(capacity = Channel.RENDEZVOUS)
|
|
||||||
.combine(presenter.progressTotalFlow) { progress, total ->
|
|
||||||
// Show hide dismiss button
|
|
||||||
binding.progressDismissBtn.isVisible = progress == total
|
|
||||||
formatProgress(progress, total)
|
|
||||||
}
|
|
||||||
.onEach {
|
|
||||||
binding.progressText.text = it
|
|
||||||
}
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
presenter.progressTotalFlow
|
|
||||||
.buffer(capacity = Channel.RENDEZVOUS)
|
|
||||||
.onEach {
|
|
||||||
binding.progressBar.max = it
|
|
||||||
}
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
presenter.progressFlow
|
|
||||||
.buffer(capacity = Channel.RENDEZVOUS)
|
|
||||||
.onEach {
|
|
||||||
binding.progressBar.progress = it
|
|
||||||
}
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
presenter.eventFlow
|
|
||||||
?.buffer(capacity = Channel.RENDEZVOUS)
|
|
||||||
?.onEach {
|
|
||||||
binding.progressLog.append("$it\n")
|
|
||||||
}
|
|
||||||
?.launchIn(this)
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
BatchAddPresenter.STATE_PROGRESS_TO_INPUT -> {
|
|
||||||
hideProgress(binding)
|
|
||||||
presenter.currentlyAddingFlow.value = BatchAddPresenter.STATE_IDLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val EhFragmentBatchAddBinding.progressViews
|
|
||||||
get() = listOf(
|
|
||||||
progressTitleView,
|
|
||||||
progressLog,
|
|
||||||
progressBar,
|
|
||||||
progressText,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val EhFragmentBatchAddBinding.inputViews
|
|
||||||
get() = listOf(
|
|
||||||
inputTitleView,
|
|
||||||
galleriesBox,
|
|
||||||
btnAddGalleries,
|
|
||||||
)
|
|
||||||
|
|
||||||
private var List<View>.isVisible: Boolean
|
|
||||||
get() = throw UnsupportedOperationException()
|
|
||||||
set(v) {
|
|
||||||
forEach { it.isVisible = v }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showProgress(target: EhFragmentBatchAddBinding = binding) {
|
|
||||||
target.apply {
|
|
||||||
viewScope.launch {
|
|
||||||
inputViews.isVisible = false
|
|
||||||
delay(0.5.seconds)
|
|
||||||
progressViews.isVisible = true
|
|
||||||
}
|
|
||||||
}.progressLog.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideProgress(target: EhFragmentBatchAddBinding = binding) {
|
|
||||||
target.apply {
|
|
||||||
viewScope.launch {
|
|
||||||
progressViews.isVisible = false
|
|
||||||
binding.progressDismissBtn.isVisible = false
|
|
||||||
delay(0.5.seconds)
|
|
||||||
inputViews.isVisible = true
|
|
||||||
}
|
|
||||||
}.galleriesBox.setText("", TextView.BufferType.EDITABLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatProgress(progress: Int, total: Int) = "$progress/$total"
|
|
||||||
|
|
||||||
private fun addGalleries(galleries: String) {
|
|
||||||
// Check text box has content
|
|
||||||
if (galleries.isBlank()) {
|
|
||||||
noGalleriesSpecified()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
presenter.addGalleries(applicationContext!!, galleries)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun noGalleriesSpecified() {
|
|
||||||
activity?.let {
|
|
||||||
MaterialAlertDialogBuilder(it)
|
|
||||||
.setTitle(R.string.batch_add_no_valid_galleries)
|
|
||||||
.setMessage(R.string.batch_add_no_valid_galleries_message)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
package exh.ui.batchadd
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
|
||||||
import exh.GalleryAddEvent
|
|
||||||
import exh.GalleryAdder
|
|
||||||
import exh.log.xLogE
|
|
||||||
import exh.util.trimOrNull
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ensureActive
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class BatchAddPresenter : BasePresenter<BatchAddController>() {
|
|
||||||
private val preferences: UnsortedPreferences by injectLazy()
|
|
||||||
|
|
||||||
private val galleryAdder by lazy { GalleryAdder() }
|
|
||||||
|
|
||||||
val progressTotalFlow = MutableStateFlow(0)
|
|
||||||
val progressFlow = MutableStateFlow(0)
|
|
||||||
var eventFlow: MutableSharedFlow<String>? = null
|
|
||||||
val currentlyAddingFlow = MutableStateFlow(STATE_IDLE)
|
|
||||||
|
|
||||||
fun addGalleries(context: Context, galleries: String) {
|
|
||||||
eventFlow = MutableSharedFlow(1)
|
|
||||||
|
|
||||||
val splitGalleries = if (ehVisitedRegex.containsMatchIn(galleries)) {
|
|
||||||
val url = if (preferences.enableExhentai().get()) {
|
|
||||||
"https://exhentai.org/g/"
|
|
||||||
} else {
|
|
||||||
"https://e-hentai.org/g/"
|
|
||||||
}
|
|
||||||
ehVisitedRegex.findAll(galleries).map { galleryKeys ->
|
|
||||||
val linkParts = galleryKeys.value.split(".")
|
|
||||||
url + linkParts[0] + "/" + linkParts[1].replace(":", "")
|
|
||||||
}.toList()
|
|
||||||
} else {
|
|
||||||
galleries.split("\n")
|
|
||||||
.mapNotNull(String::trimOrNull)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressFlow.value = 0
|
|
||||||
progressTotalFlow.value = splitGalleries.size
|
|
||||||
|
|
||||||
currentlyAddingFlow.value = STATE_INPUT_TO_PROGRESS
|
|
||||||
|
|
||||||
val handler = CoroutineExceptionHandler { _, throwable ->
|
|
||||||
xLogE("Batch add error", throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
presenterScope.launch(Dispatchers.IO + handler) {
|
|
||||||
val succeeded = mutableListOf<String>()
|
|
||||||
val failed = mutableListOf<String>()
|
|
||||||
|
|
||||||
splitGalleries.forEachIndexed { i, s ->
|
|
||||||
ensureActive()
|
|
||||||
val result = withIOContext { galleryAdder.addGallery(context, s, true) }
|
|
||||||
if (result is GalleryAddEvent.Success) {
|
|
||||||
succeeded.add(s)
|
|
||||||
} else {
|
|
||||||
failed.add(s)
|
|
||||||
}
|
|
||||||
progressFlow.value = i + 1
|
|
||||||
eventFlow?.emit(
|
|
||||||
(
|
|
||||||
when (result) {
|
|
||||||
is GalleryAddEvent.Success -> context.getString(R.string.batch_add_ok)
|
|
||||||
is GalleryAddEvent.Fail -> context.getString(R.string.batch_add_error)
|
|
||||||
}
|
|
||||||
) + " " + result.logMessage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show report
|
|
||||||
val summary = context.getString(R.string.batch_add_summary, succeeded.size, failed.size)
|
|
||||||
eventFlow?.emit(summary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val STATE_IDLE = 0
|
|
||||||
const val STATE_INPUT_TO_PROGRESS = 1
|
|
||||||
const val STATE_PROGRESS_TO_INPUT = 2
|
|
||||||
|
|
||||||
val ehVisitedRegex = """[0-9]*?\.[a-z0-9]*?:""".toRegex()
|
|
||||||
}
|
|
||||||
}
|
|
174
app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt
Normal file
174
app/src/main/java/exh/ui/batchadd/BatchAddScreen.kt
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package exh.ui.batchadd
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.Button
|
||||||
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.LocalRouter
|
||||||
|
import eu.kanade.presentation.util.padding
|
||||||
|
import eu.kanade.presentation.util.plus
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
class BatchAddScreen : Screen {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val screenModel = rememberScreenModel { BatchAddScreenModel() }
|
||||||
|
val state by screenModel.state.collectAsState()
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val router = LocalRouter.currentOrThrow
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(R.string.batch_add),
|
||||||
|
navigateUp = {
|
||||||
|
when {
|
||||||
|
navigator.canPop -> navigator.pop()
|
||||||
|
else -> router.popCurrentController()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
when (state.state) {
|
||||||
|
BatchAddScreenModel.State.INPUT -> {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.eh_batch_add_title), style = MaterialTheme.typography.titleLarge)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
TextField(
|
||||||
|
value = state.galleries,
|
||||||
|
onValueChange = screenModel::updateGalleries,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.eh_batch_add_description),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(autoCorrect = false),
|
||||||
|
textStyle = MaterialTheme.typography.bodyLarge,
|
||||||
|
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { screenModel.addGalleries(context) },
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.eh_batch_add_button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BatchAddScreenModel.State.PROGRESS -> {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = paddingValues + PaddingValues(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
item(key = "top") {
|
||||||
|
Column {
|
||||||
|
Text(text = stringResource(R.string.eh_batch_add_adding_galleries), style = MaterialTheme.typography.titleLarge)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = state.progress.toFloat() / state.progressTotal,
|
||||||
|
Modifier
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.weight(1f),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = state.progress.toString() + "/" + state.progressTotal,
|
||||||
|
modifier = Modifier.weight(0.15f),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(
|
||||||
|
state.events,
|
||||||
|
key = { index, text -> index + text.hashCode() },
|
||||||
|
) { _, text ->
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (state.progress == state.progressTotal) {
|
||||||
|
item(key = "finish") {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = screenModel::finish,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.eh_batch_add_finish))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onDismissRequest = screenModel::dismissDialog
|
||||||
|
when (state.dialog) {
|
||||||
|
BatchAddScreenModel.Dialog.NoGalleriesSpecified -> AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.batch_add_no_valid_galleries))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.batch_add_no_valid_galleries_message))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
null -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
app/src/main/java/exh/ui/batchadd/BatchAddScreenModel.kt
Normal file
141
app/src/main/java/exh/ui/batchadd/BatchAddScreenModel.kt
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package exh.ui.batchadd
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
|
import exh.GalleryAddEvent
|
||||||
|
import exh.GalleryAdder
|
||||||
|
import exh.log.xLogE
|
||||||
|
import exh.util.trimOrNull
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class BatchAddScreenModel(
|
||||||
|
private val unsortedPreferences: UnsortedPreferences = Injekt.get(),
|
||||||
|
) : StateScreenModel<BatchAddState>(BatchAddState()) {
|
||||||
|
private val galleryAdder by lazy { GalleryAdder() }
|
||||||
|
|
||||||
|
fun addGalleries(context: Context) {
|
||||||
|
val galleries = state.value.galleries
|
||||||
|
// Check text box has content
|
||||||
|
if (galleries.isBlank()) {
|
||||||
|
mutableState.update { it.copy(dialog = Dialog.NoGalleriesSpecified) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addGalleries(context, galleries)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addGalleries(context: Context, galleries: String) {
|
||||||
|
val splitGalleries = if (ehVisitedRegex.containsMatchIn(galleries)) {
|
||||||
|
val url = if (unsortedPreferences.enableExhentai().get()) {
|
||||||
|
"https://exhentai.org/g/"
|
||||||
|
} else {
|
||||||
|
"https://e-hentai.org/g/"
|
||||||
|
}
|
||||||
|
ehVisitedRegex.findAll(galleries).map { galleryKeys ->
|
||||||
|
val linkParts = galleryKeys.value.split(".")
|
||||||
|
url + linkParts[0] + "/" + linkParts[1].replace(":", "")
|
||||||
|
}.toList()
|
||||||
|
} else {
|
||||||
|
galleries.split("\n")
|
||||||
|
.mapNotNull(String::trimOrNull)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
progress = 0,
|
||||||
|
progressTotal = splitGalleries.size,
|
||||||
|
state = State.PROGRESS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
xLogE("Batch add error", throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch(Dispatchers.IO + handler) {
|
||||||
|
val succeeded = mutableListOf<String>()
|
||||||
|
val failed = mutableListOf<String>()
|
||||||
|
|
||||||
|
splitGalleries.forEachIndexed { i, s ->
|
||||||
|
ensureActive()
|
||||||
|
val result = withIOContext { galleryAdder.addGallery(context, s, true) }
|
||||||
|
if (result is GalleryAddEvent.Success) {
|
||||||
|
succeeded.add(s)
|
||||||
|
} else {
|
||||||
|
failed.add(s)
|
||||||
|
}
|
||||||
|
mutableState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
progress = i + 1,
|
||||||
|
events = state.events.plus(
|
||||||
|
when (result) {
|
||||||
|
is GalleryAddEvent.Success -> context.getString(R.string.batch_add_ok)
|
||||||
|
is GalleryAddEvent.Fail -> context.getString(R.string.batch_add_error)
|
||||||
|
} + " " + result.logMessage,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show report
|
||||||
|
val summary = context.getString(R.string.batch_add_summary, succeeded.size, failed.size)
|
||||||
|
mutableState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
events = state.events + summary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finish() {
|
||||||
|
mutableState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
progressTotal = 0,
|
||||||
|
progress = 0,
|
||||||
|
galleries = "",
|
||||||
|
state = State.INPUT,
|
||||||
|
events = emptyList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateGalleries(galleries: String) {
|
||||||
|
mutableState.update { it.copy(galleries = galleries) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissDialog() {
|
||||||
|
mutableState.update { it.copy(dialog = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
INPUT,
|
||||||
|
PROGRESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Dialog {
|
||||||
|
object NoGalleriesSpecified : Dialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ehVisitedRegex = """[0-9]*?\.[a-z0-9]*?:""".toRegex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BatchAddState(
|
||||||
|
val progressTotal: Int = 0,
|
||||||
|
val progress: Int = 0,
|
||||||
|
val galleries: String = "",
|
||||||
|
val state: BatchAddScreenModel.State = BatchAddScreenModel.State.INPUT,
|
||||||
|
val events: List<String> = emptyList(),
|
||||||
|
val dialog: BatchAddScreenModel.Dialog? = null,
|
||||||
|
)
|
@ -1,115 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:animateLayoutChanges="true"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/input_title_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/eh_batch_add_title"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
|
||||||
android:id="@+id/galleries_box"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:ems="10"
|
|
||||||
android:gravity="top"
|
|
||||||
android:hint="@string/eh_batch_add_description"
|
|
||||||
android:inputType="textUri|textMultiLine|textNoSuggestions"
|
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/input_title_view" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_add_galleries"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="@string/eh_batch_add_button"
|
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/galleries_box" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/progress_title_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/eh_batch_add_adding_galleries"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/progress_bar_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/progress_title_view">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress_bar"
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:paddingTop="2dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/progress_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="0.15"
|
|
||||||
android:background="#00000000"
|
|
||||||
android:scrollHorizontally="false"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:visibility="gone" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/progress_log"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/progress_bar_text" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/progress_dismiss_btn"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="@string/eh_batch_add_finish"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/progress_log" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
Loading…
x
Reference in New Issue
Block a user