Convert mass migration to compose
This commit is contained in:
parent
aaddb4bf00
commit
e696b95330
@ -1,10 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import java.text.DecimalFormat
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class MigratingManga(
|
class MigratingManga(
|
||||||
@ -12,9 +15,6 @@ class MigratingManga(
|
|||||||
val chapterInfo: ChapterInfo,
|
val chapterInfo: ChapterInfo,
|
||||||
val sourcesString: String,
|
val sourcesString: String,
|
||||||
parentContext: CoroutineContext,
|
parentContext: CoroutineContext,
|
||||||
val getManga: suspend (SearchResult.Result) -> Manga?,
|
|
||||||
val getChapterInfo: suspend (SearchResult.Result) -> ChapterInfo,
|
|
||||||
val getSourceName: (Manga) -> String,
|
|
||||||
) {
|
) {
|
||||||
val migrationScope = CoroutineScope(parentContext + SupervisorJob() + Dispatchers.Default)
|
val migrationScope = CoroutineScope(parentContext + SupervisorJob() + Dispatchers.Default)
|
||||||
|
|
||||||
@ -32,10 +32,19 @@ class MigratingManga(
|
|||||||
data class ChapterInfo(
|
data class ChapterInfo(
|
||||||
val latestChapter: Float?,
|
val latestChapter: Float?,
|
||||||
val chapterCount: Int,
|
val chapterCount: Int,
|
||||||
)
|
) {
|
||||||
|
fun getFormattedLatestChapter(context: Context): String {
|
||||||
fun toModal(): MigrationProcessItem {
|
return if (latestChapter != null && latestChapter > 0f) {
|
||||||
// Create the model object.
|
context.getString(
|
||||||
return MigrationProcessItem(this)
|
R.string.latest_,
|
||||||
|
DecimalFormat("#.#").format(latestChapter),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.getString(
|
||||||
|
R.string.latest_,
|
||||||
|
context.getString(R.string.unknown),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,70 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import androidx.compose.foundation.Image
|
||||||
import android.view.Menu
|
import androidx.compose.foundation.background
|
||||||
import android.view.MenuInflater
|
import androidx.compose.foundation.clickable
|
||||||
import android.view.MenuItem
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import android.view.View
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowForward
|
||||||
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.ContentCopy
|
||||||
|
import androidx.compose.material.icons.outlined.CopyAll
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
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.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.Badge
|
||||||
|
import eu.kanade.presentation.components.BadgeGroup
|
||||||
|
import eu.kanade.presentation.components.MangaCover
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
|
import eu.kanade.presentation.util.plus
|
||||||
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
|
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationListControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler
|
import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.MigrationMangaDialog
|
import eu.kanade.tachiyomi.ui.browse.migration.MigrationMangaDialog
|
||||||
@ -26,14 +72,13 @@ import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationContr
|
|||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import eu.kanade.tachiyomi.util.system.getParcelableCompat
|
import eu.kanade.tachiyomi.util.system.getParcelableCompat
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import me.saket.cascade.CascadeDropdownMenu
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
|
|
||||||
class MigrationListController(bundle: Bundle? = null) :
|
class MigrationListController(bundle: Bundle? = null) :
|
||||||
NucleusController<MigrationListControllerBinding, MigrationListPresenter>(bundle) {
|
FullComposeController<MigrationListPresenter>(bundle) {
|
||||||
|
|
||||||
constructor(config: MigrationProcedureConfig) : this(
|
constructor(config: MigrationProcedureConfig) : this(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@ -41,63 +86,337 @@ class MigrationListController(bundle: Bundle? = null) :
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adapter: MigrationProcessAdapter? = null
|
|
||||||
|
|
||||||
val config = args.getParcelableCompat<MigrationProcedureConfig>(CONFIG_EXTRA)
|
val config = args.getParcelableCompat<MigrationProcedureConfig>(CONFIG_EXTRA)
|
||||||
|
|
||||||
private var selectedMangaId: Long? = null
|
private var selectedMangaId: Long? = null
|
||||||
private var manualMigrations = 0
|
private var manualMigrations = 0
|
||||||
|
|
||||||
override fun getTitle(): String {
|
|
||||||
val notFinished = presenter.migratingItems.value.count {
|
|
||||||
it.searchResult.value != SearchResult.Searching
|
|
||||||
}
|
|
||||||
val total = presenter.migratingItems.value.size
|
|
||||||
return activity?.getString(R.string.migration) + " ($notFinished/$total)"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPresenter(): MigrationListPresenter {
|
override fun createPresenter(): MigrationListPresenter {
|
||||||
return MigrationListPresenter(config!!)
|
return MigrationListPresenter(config!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) = MigrationListControllerBinding.inflate(inflater)
|
@Composable
|
||||||
|
override fun ComposeContent() {
|
||||||
|
val items by presenter.migratingItems.collectAsState()
|
||||||
|
val migrationDone by presenter.migrationDone.collectAsState()
|
||||||
|
val unfinishedCount by presenter.unfinishedCount.collectAsState()
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
val titleString = stringResource(R.string.migration)
|
||||||
|
val title by produceState(initialValue = titleString, items, unfinishedCount, titleString) {
|
||||||
|
withIOContext {
|
||||||
|
value = "$titleString ($unfinishedCount/${items.size})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppBar(
|
||||||
|
title = title,
|
||||||
|
actions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { openMigrationDialog(true) },
|
||||||
|
enabled = migrationDone,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (items.size == 1) Icons.Outlined.ContentCopy else Icons.Outlined.CopyAll,
|
||||||
|
contentDescription = stringResource(R.string.copy),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = { openMigrationDialog(false) },
|
||||||
|
enabled = migrationDone,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (items.size == 1) Icons.Outlined.Done else Icons.Outlined.DoneAll,
|
||||||
|
contentDescription = stringResource(R.string.migrate),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
ScrollbarLazyColumn(
|
||||||
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
|
) {
|
||||||
|
items(items, key = { it.manga.id }) { migrationItem ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateItemPlacement()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
val result by migrationItem.searchResult.collectAsState()
|
||||||
|
MigrationItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.weight(1f),
|
||||||
|
manga = migrationItem.manga,
|
||||||
|
sourcesString = migrationItem.sourcesString,
|
||||||
|
chapterInfo = migrationItem.chapterInfo,
|
||||||
|
onClick = {
|
||||||
|
router.pushController(
|
||||||
|
MangaController(
|
||||||
|
migrationItem.manga.id,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
Icon(
|
||||||
super.onViewCreated(view)
|
Icons.Outlined.ArrowForward,
|
||||||
|
contentDescription = stringResource(R.string.migrating_to),
|
||||||
|
modifier = Modifier.weight(0.2f),
|
||||||
|
)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
MigrationItemResult(
|
||||||
type(navigationBars = true) {
|
modifier = Modifier
|
||||||
padding()
|
.padding(top = 8.dp)
|
||||||
|
.weight(1f),
|
||||||
|
migrationItem = migrationItem,
|
||||||
|
result = result,
|
||||||
|
)
|
||||||
|
|
||||||
|
MigrationActionIcon(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.2f),
|
||||||
|
result = result,
|
||||||
|
skipManga = { presenter.removeManga(migrationItem.manga.id) },
|
||||||
|
searchManually = {
|
||||||
|
val manga = migrationItem.manga
|
||||||
|
selectedMangaId = manga.id
|
||||||
|
val sources = presenter.getMigrationSources()
|
||||||
|
val validSources = if (sources.size == 1) {
|
||||||
|
sources
|
||||||
|
} else {
|
||||||
|
sources.filter { it.id != manga.source }
|
||||||
|
}
|
||||||
|
val searchController = SearchController(manga, validSources)
|
||||||
|
searchController.targetController = this@MigrationListController
|
||||||
|
router.pushController(searchController)
|
||||||
|
},
|
||||||
|
migrateNow = {
|
||||||
|
migrateManga(migrationItem.manga.id, false)
|
||||||
|
manualMigrations++
|
||||||
|
},
|
||||||
|
copyNow = {
|
||||||
|
migrateManga(migrationItem.manga.id, true)
|
||||||
|
manualMigrations++
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle()
|
|
||||||
|
|
||||||
adapter = MigrationProcessAdapter(this)
|
|
||||||
|
|
||||||
binding.recycler.adapter = adapter
|
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
|
||||||
binding.recycler.setHasFixedSize(true)
|
|
||||||
|
|
||||||
presenter.migratingItems
|
|
||||||
.onEach {
|
|
||||||
adapter?.updateDataSet(it.map { it.toModal() })
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateCount() {
|
|
||||||
if (router.backstack.lastOrNull()?.controller == this@MigrationListController) {
|
|
||||||
setTitle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableButtons() {
|
@Composable
|
||||||
activity?.invalidateOptionsMenu()
|
fun MigrationItem(
|
||||||
|
modifier: Modifier,
|
||||||
|
manga: Manga,
|
||||||
|
sourcesString: String,
|
||||||
|
chapterInfo: MigratingManga.ChapterInfo,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
.widthIn(max = 150.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(4.dp),
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Box(
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.aspectRatio(MangaCover.Book.ratio),
|
||||||
|
) {
|
||||||
|
MangaCover.Book(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
data = manga,
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
1f to Color(0xAA000000),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.fillMaxHeight(0.33f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.align(Alignment.BottomStart),
|
||||||
|
text = manga.title.ifBlank { stringResource(R.string.unknown) },
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 18.sp,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleSmall.copy(
|
||||||
|
color = Color.White,
|
||||||
|
shadow = Shadow(
|
||||||
|
color = Color.Black,
|
||||||
|
blurRadius = 4f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
BadgeGroup(modifier = Modifier.padding(4.dp)) {
|
||||||
|
Badge(text = "${chapterInfo.chapterCount}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = sourcesString,
|
||||||
|
modifier = Modifier.padding(top = 4.dp, bottom = 1.dp, start = 8.dp),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
val formattedLatestChapter by produceState(initialValue = "") {
|
||||||
|
value = withIOContext {
|
||||||
|
chapterInfo.getFormattedLatestChapter(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = formattedLatestChapter,
|
||||||
|
modifier = Modifier.padding(top = 1.dp, bottom = 4.dp, start = 8.dp),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrationActionIcon(
|
||||||
|
modifier: Modifier,
|
||||||
|
result: SearchResult,
|
||||||
|
skipManga: () -> Unit,
|
||||||
|
searchManually: () -> Unit,
|
||||||
|
migrateNow: () -> Unit,
|
||||||
|
copyNow: () -> Unit,
|
||||||
|
) {
|
||||||
|
var moreExpanded by remember { mutableStateOf(false) }
|
||||||
|
val closeMenu = { moreExpanded = false }
|
||||||
|
|
||||||
|
Box(modifier) {
|
||||||
|
if (result is SearchResult.Searching) {
|
||||||
|
IconButton(onClick = skipManga) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Close,
|
||||||
|
contentDescription = stringResource(R.string.action_stop),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (result is SearchResult.Result || result is SearchResult.NotFound) {
|
||||||
|
IconButton(onClick = { moreExpanded = !moreExpanded }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.MoreVert,
|
||||||
|
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CascadeDropdownMenu(
|
||||||
|
expanded = moreExpanded,
|
||||||
|
onDismissRequest = closeMenu,
|
||||||
|
offset = DpOffset(8.dp, (-56).dp),
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.action_search_manually)) },
|
||||||
|
onClick = {
|
||||||
|
searchManually()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.action_skip_manga)) },
|
||||||
|
onClick = {
|
||||||
|
skipManga()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (result is SearchResult.Result) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.action_migrate_now)) },
|
||||||
|
onClick = {
|
||||||
|
migrateNow()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.action_copy_now)) },
|
||||||
|
onClick = {
|
||||||
|
copyNow()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrationItemResult(modifier: Modifier, migrationItem: MigratingManga, result: SearchResult) {
|
||||||
|
Box(modifier) {
|
||||||
|
when (result) {
|
||||||
|
SearchResult.Searching -> Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(max = 150.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(MangaCover.Book.ratio),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
SearchResult.NotFound -> Image(
|
||||||
|
painter = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
)
|
||||||
|
is SearchResult.Result -> {
|
||||||
|
val item by produceState<Triple<Manga, MigratingManga.ChapterInfo, String>?>(
|
||||||
|
initialValue = null,
|
||||||
|
migrationItem,
|
||||||
|
result,
|
||||||
|
) {
|
||||||
|
value = withIOContext {
|
||||||
|
val manga = presenter.getManga(result) ?: return@withIOContext null
|
||||||
|
Triple(
|
||||||
|
manga,
|
||||||
|
presenter.getChapterInfo(result),
|
||||||
|
presenter.getSourceName(manga),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item != null) {
|
||||||
|
val (manga, chapterInfo, source) = item!!
|
||||||
|
MigrationItem(
|
||||||
|
modifier = Modifier,
|
||||||
|
manga = manga,
|
||||||
|
sourcesString = source,
|
||||||
|
chapterInfo = chapterInfo,
|
||||||
|
onClick = {
|
||||||
|
router.pushController(
|
||||||
|
MangaController(
|
||||||
|
manga.id,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun noMigration() {
|
private fun noMigration() {
|
||||||
@ -116,36 +435,6 @@ class MigrationListController(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMenuItemClick(mangaId: Long, item: MenuItem) {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_search_manually -> {
|
|
||||||
val manga = presenter.migratingItems.value
|
|
||||||
.find { it.manga.id == mangaId }
|
|
||||||
?.manga
|
|
||||||
?: return
|
|
||||||
selectedMangaId = mangaId
|
|
||||||
val sources = presenter.getMigrationSources()
|
|
||||||
val validSources = if (sources.size == 1) {
|
|
||||||
sources
|
|
||||||
} else {
|
|
||||||
sources.filter { it.id != manga.source }
|
|
||||||
}
|
|
||||||
val searchController = SearchController(manga, validSources)
|
|
||||||
searchController.targetController = this@MigrationListController
|
|
||||||
router.pushController(searchController)
|
|
||||||
}
|
|
||||||
R.id.action_skip -> presenter.removeManga(mangaId)
|
|
||||||
R.id.action_migrate_now -> {
|
|
||||||
migrateManga(mangaId, false)
|
|
||||||
manualMigrations++
|
|
||||||
}
|
|
||||||
R.id.action_copy_now -> {
|
|
||||||
migrateManga(mangaId, true)
|
|
||||||
manualMigrations++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun useMangaForMigration(manga: Manga, source: Source) {
|
fun useMangaForMigration(manga: Manga, source: Source) {
|
||||||
presenter.useMangaForMigration(manga, source, selectedMangaId ?: return)
|
presenter.useMangaForMigration(manga, source, selectedMangaId ?: return)
|
||||||
}
|
}
|
||||||
@ -167,9 +456,7 @@ class MigrationListController(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sourceFinished() {
|
fun sourceFinished() {
|
||||||
updateCount()
|
|
||||||
if (presenter.migratingItems.value.isEmpty()) noMigration()
|
if (presenter.migratingItems.value.isEmpty()) noMigration()
|
||||||
if (presenter.allMangasDone()) enableButtons()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateOut(manga: Manga?) {
|
fun navigateOut(manga: Manga?) {
|
||||||
@ -198,61 +485,15 @@ class MigrationListController(bundle: Bundle? = null) :
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
private fun openMigrationDialog(copy: Boolean) {
|
||||||
inflater.inflate(R.menu.migration_list, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
||||||
// Initialize menu items.
|
|
||||||
|
|
||||||
val allMangasDone = presenter.allMangasDone()
|
|
||||||
|
|
||||||
val menuCopy = menu.findItem(R.id.action_copy_manga)
|
|
||||||
val menuMigrate = menu.findItem(R.id.action_migrate_manga)
|
|
||||||
|
|
||||||
if (presenter.migratingItems.value.size == 1) {
|
|
||||||
menuMigrate.icon = VectorDrawableCompat.create(
|
|
||||||
resources!!,
|
|
||||||
R.drawable.ic_done_24dp,
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val tintColor = activity?.getResourceColor(R.attr.colorOnSurface) ?: Color.WHITE
|
|
||||||
val color = if (allMangasDone) {
|
|
||||||
tintColor
|
|
||||||
} else {
|
|
||||||
ColorUtils.setAlphaComponent(tintColor, 127)
|
|
||||||
}
|
|
||||||
menuCopy.setIconTint(allMangasDone, color)
|
|
||||||
menuMigrate.setIconTint(allMangasDone, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MenuItem.setIconTint(enabled: Boolean, color: Int) {
|
|
||||||
icon?.mutate()
|
|
||||||
icon?.setTint(color)
|
|
||||||
isEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
val totalManga = presenter.migratingItems.value.size
|
val totalManga = presenter.migratingItems.value.size
|
||||||
val mangaSkipped = presenter.mangasSkipped()
|
val mangaSkipped = presenter.mangasSkipped()
|
||||||
when (item.itemId) {
|
MigrationMangaDialog(
|
||||||
R.id.action_copy_manga -> MigrationMangaDialog(
|
this,
|
||||||
this,
|
copy,
|
||||||
true,
|
totalManga,
|
||||||
totalManga,
|
mangaSkipped,
|
||||||
mangaSkipped,
|
).showDialog(router)
|
||||||
).showDialog(router)
|
|
||||||
R.id.action_migrate_manga -> MigrationMangaDialog(
|
|
||||||
this,
|
|
||||||
false,
|
|
||||||
totalManga,
|
|
||||||
mangaSkipped,
|
|
||||||
).showDialog(router)
|
|
||||||
else -> return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -83,6 +83,8 @@ class MigrationListPresenter(
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
val migratingItems = MutableStateFlow<List<MigratingManga>>(emptyList())
|
val migratingItems = MutableStateFlow<List<MigratingManga>>(emptyList())
|
||||||
|
val migrationDone = MutableStateFlow(false)
|
||||||
|
val unfinishedCount = MutableStateFlow(0)
|
||||||
|
|
||||||
val hideNotFound = preferences.hideNotFoundMigration().get()
|
val hideNotFound = preferences.hideNotFoundMigration().get()
|
||||||
|
|
||||||
@ -108,9 +110,6 @@ class MigrationListPresenter(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
parentContext = presenterScope.coroutineContext,
|
parentContext = presenterScope.coroutineContext,
|
||||||
getManga = ::getManga,
|
|
||||||
getChapterInfo = ::getChapterInfo,
|
|
||||||
getSourceName = ::getSourceName,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,15 +123,15 @@ class MigrationListPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getManga(result: SearchResult.Result) = getManga.await(result.id)
|
suspend fun getManga(result: SearchResult.Result) = getManga.await(result.id)
|
||||||
private suspend fun getChapterInfo(result: SearchResult.Result) = getChapterInfo(result.id)
|
suspend fun getChapterInfo(result: SearchResult.Result) = getChapterInfo(result.id)
|
||||||
private suspend fun getChapterInfo(id: Long) = getChapterByMangaId.await(id).let { chapters ->
|
suspend fun getChapterInfo(id: Long) = getChapterByMangaId.await(id).let { chapters ->
|
||||||
MigratingManga.ChapterInfo(
|
MigratingManga.ChapterInfo(
|
||||||
latestChapter = chapters.maxOfOrNull { it.chapterNumber },
|
latestChapter = chapters.maxOfOrNull { it.chapterNumber },
|
||||||
chapterCount = chapters.size,
|
chapterCount = chapters.size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private fun getSourceName(manga: Manga) = sourceManager.getOrStub(manga.source).getNameForMangaInfo(null)
|
fun getSourceName(manga: Manga) = sourceManager.getOrStub(manga.source).getNameForMangaInfo(null)
|
||||||
|
|
||||||
fun getMigrationSources() = preferences.migrationSources().get().split("/").mapNotNull {
|
fun getMigrationSources() = preferences.migrationSources().get().split("/").mapNotNull {
|
||||||
val value = it.toLongOrNull() ?: return@mapNotNull null
|
val value = it.toLongOrNull() ?: return@mapNotNull null
|
||||||
@ -141,6 +140,7 @@ class MigrationListPresenter(
|
|||||||
|
|
||||||
private suspend fun runMigrations(mangas: List<MigratingManga>) {
|
private suspend fun runMigrations(mangas: List<MigratingManga>) {
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
|
unfinishedCount.value = mangas.size
|
||||||
val useSourceWithMost = preferences.useSourceWithMost().get()
|
val useSourceWithMost = preferences.useSourceWithMost().get()
|
||||||
val useSmartSearch = preferences.smartMigration().get()
|
val useSmartSearch = preferences.smartMigration().get()
|
||||||
|
|
||||||
@ -269,13 +269,21 @@ class MigrationListPresenter(
|
|||||||
if (result == null && hideNotFound) {
|
if (result == null && hideNotFound) {
|
||||||
removeManga(manga)
|
removeManga(manga)
|
||||||
}
|
}
|
||||||
withUIContext {
|
sourceFinished()
|
||||||
view?.sourceFinished()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun sourceFinished() {
|
||||||
|
unfinishedCount.value = migratingItems.value.count {
|
||||||
|
it.searchResult.value != SearchResult.Searching
|
||||||
|
}
|
||||||
|
if (allMangasDone()) {
|
||||||
|
migrationDone.value = true
|
||||||
|
}
|
||||||
|
withUIContext { view?.sourceFinished() }
|
||||||
|
}
|
||||||
|
|
||||||
fun allMangasDone() = migratingItems.value.all { it.searchResult.value != SearchResult.Searching } &&
|
fun allMangasDone() = migratingItems.value.all { it.searchResult.value != SearchResult.Searching } &&
|
||||||
migratingItems.value.any { it.searchResult.value is SearchResult.Result }
|
migratingItems.value.any { it.searchResult.value is SearchResult.Result }
|
||||||
|
|
||||||
@ -292,8 +300,8 @@ class MigrationListPresenter(
|
|||||||
// Update chapters read
|
// Update chapters read
|
||||||
if (MigrationFlags.hasChapters(flags)) {
|
if (MigrationFlags.hasChapters(flags)) {
|
||||||
val prevMangaChapters = getChapterByMangaId.await(prevManga.id)
|
val prevMangaChapters = getChapterByMangaId.await(prevManga.id)
|
||||||
val maxChapterRead =
|
val maxChapterRead = prevMangaChapters.filter(Chapter::read)
|
||||||
prevMangaChapters.filter(Chapter::read).maxOfOrNull(Chapter::chapterNumber)
|
.maxOfOrNull(Chapter::chapterNumber)
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
val dbChapters = getChapterByMangaId.await(manga.id)
|
||||||
val prevHistoryList = getHistoryByMangaId.await(prevManga.id)
|
val prevHistoryList = getHistoryByMangaId.await(prevManga.id)
|
||||||
|
|
||||||
@ -489,16 +497,12 @@ class MigrationListPresenter(
|
|||||||
if (migratingItems.value.size == 1) {
|
if (migratingItems.value.size == 1) {
|
||||||
item.searchResult.value = SearchResult.NotFound
|
item.searchResult.value = SearchResult.NotFound
|
||||||
item.migrationScope.cancel()
|
item.migrationScope.cancel()
|
||||||
withUIContext {
|
sourceFinished()
|
||||||
view?.sourceFinished()
|
|
||||||
}
|
|
||||||
return@launchIO
|
return@launchIO
|
||||||
}
|
}
|
||||||
removeManga(item)
|
removeManga(item)
|
||||||
item.migrationScope.cancel()
|
item.migrationScope.cancel()
|
||||||
withUIContext {
|
sourceFinished()
|
||||||
view?.sourceFinished()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
|
|
||||||
class MigrationProcessAdapter(
|
|
||||||
val controller: MigrationListController,
|
|
||||||
) : FlexibleAdapter<MigrationProcessItem>(null, controller, true)
|
|
@ -1,210 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.PopupMenu
|
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import coil.dispose
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationMangaCardBinding
|
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationProcessItemBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.ChapterInfo
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigratingManga.SearchResult
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.view.loadAutoPause
|
|
||||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.catch
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import reactivecircus.flowbinding.android.view.clicks
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
|
|
||||||
class MigrationProcessHolder(
|
|
||||||
view: View,
|
|
||||||
private val adapter: MigrationProcessAdapter,
|
|
||||||
) : FlexibleViewHolder(view, adapter) {
|
|
||||||
private var item: MigrationProcessItem? = null
|
|
||||||
private val binding = MigrationProcessItemBinding.bind(view)
|
|
||||||
|
|
||||||
private val jobs = CopyOnWriteArrayList<Job>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
|
||||||
// correctly positioned. The reason being that the view may change position before the
|
|
||||||
// PopupMenu is shown.
|
|
||||||
binding.migrationMenu.setOnClickListener { it.post { showPopupMenu(it) } }
|
|
||||||
binding.skipManga.setOnClickListener { it.post { adapter.controller.removeManga(item?.manga?.manga?.id ?: return@post) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(item: MigrationProcessItem) {
|
|
||||||
this.item = item
|
|
||||||
jobs.removeAll { it.cancel(); true }
|
|
||||||
jobs += adapter.controller.viewScope.launchUI {
|
|
||||||
val migrateManga = item.manga
|
|
||||||
val manga = migrateManga.manga
|
|
||||||
|
|
||||||
binding.migrationMenu.setVectorCompat(
|
|
||||||
R.drawable.ic_more_24dp,
|
|
||||||
R.attr.colorOnPrimary,
|
|
||||||
)
|
|
||||||
binding.skipManga.setVectorCompat(
|
|
||||||
R.drawable.ic_close_24dp,
|
|
||||||
R.attr.colorOnPrimary,
|
|
||||||
)
|
|
||||||
binding.migrationMenu.isInvisible = true
|
|
||||||
binding.skipManga.isVisible = true
|
|
||||||
binding.migrationMangaCardTo.resetManga()
|
|
||||||
binding.migrationMangaCardFrom.attachManga(manga, item.manga.sourcesString, item.manga.chapterInfo)
|
|
||||||
jobs += binding.migrationMangaCardFrom.root.clicks()
|
|
||||||
.onEach {
|
|
||||||
adapter.controller.router.pushController(
|
|
||||||
MangaController(
|
|
||||||
manga.id,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.launchIn(adapter.controller.viewScope)
|
|
||||||
|
|
||||||
/*launchUI {
|
|
||||||
item.manga.progress.asFlow().collect { (max, progress) ->
|
|
||||||
withUIContext {
|
|
||||||
migration_manga_card_to.search_progress.let { progressBar ->
|
|
||||||
progressBar.max = max
|
|
||||||
progressBar.progress = progress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
jobs += migrateManga.searchResult
|
|
||||||
.onEach { searchResult ->
|
|
||||||
this@MigrationProcessHolder.logcat { (searchResult to (migrateManga.manga.id to this@MigrationProcessHolder.item?.manga?.manga?.id)).toString() }
|
|
||||||
if (migrateManga.manga.id != this@MigrationProcessHolder.item?.manga?.manga?.id ||
|
|
||||||
searchResult == SearchResult.Searching
|
|
||||||
) {
|
|
||||||
return@onEach
|
|
||||||
}
|
|
||||||
|
|
||||||
val resultManga = withIOContext {
|
|
||||||
(searchResult as? SearchResult.Result)
|
|
||||||
?.let { migrateManga.getManga(it) }
|
|
||||||
}
|
|
||||||
if (resultManga != null) {
|
|
||||||
val (sourceName, latestChapter) = withIOContext {
|
|
||||||
val sourceNameAsync = async { migrateManga.getSourceName(resultManga) }
|
|
||||||
val latestChapterAsync = async { migrateManga.getChapterInfo(searchResult as SearchResult.Result) }
|
|
||||||
sourceNameAsync.await() to latestChapterAsync.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.migrationMangaCardTo.attachManga(resultManga, sourceName, latestChapter)
|
|
||||||
jobs += binding.migrationMangaCardTo.root.clicks()
|
|
||||||
.onEach {
|
|
||||||
adapter.controller.router.pushController(
|
|
||||||
MangaController(
|
|
||||||
resultManga.id,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.launchIn(adapter.controller.viewScope)
|
|
||||||
} else {
|
|
||||||
binding.migrationMangaCardTo.progress.isVisible = false
|
|
||||||
binding.migrationMangaCardTo.title.text = itemView.context
|
|
||||||
.getString(R.string.no_alternatives_found)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.migrationMenu.isVisible = true
|
|
||||||
binding.skipManga.isVisible = false
|
|
||||||
adapter.controller.sourceFinished()
|
|
||||||
}
|
|
||||||
.catch {
|
|
||||||
this@MigrationProcessHolder.logcat(throwable = it) { "Error updating result info" }
|
|
||||||
}
|
|
||||||
.launchIn(adapter.controller.viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MigrationMangaCardBinding.resetManga() {
|
|
||||||
progress.isVisible = true
|
|
||||||
thumbnail.dispose()
|
|
||||||
thumbnail.setImageDrawable(null)
|
|
||||||
title.text = ""
|
|
||||||
mangaSourceLabel.text = ""
|
|
||||||
badges.unreadText.text = ""
|
|
||||||
badges.unreadText.isVisible = false
|
|
||||||
mangaLastChapterLabel.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MigrationMangaCardBinding.attachManga(
|
|
||||||
manga: Manga,
|
|
||||||
sourceString: String,
|
|
||||||
chapterInfo: ChapterInfo,
|
|
||||||
) {
|
|
||||||
progress.isVisible = false
|
|
||||||
thumbnail.loadAutoPause(manga)
|
|
||||||
|
|
||||||
title.text = if (manga.title.isBlank()) {
|
|
||||||
itemView.context.getString(R.string.unknown)
|
|
||||||
} else {
|
|
||||||
manga.ogTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
mangaSourceLabel.text = sourceString
|
|
||||||
|
|
||||||
// For rounded corners
|
|
||||||
badges.leftBadges.clipToOutline = true
|
|
||||||
badges.rightBadges.clipToOutline = true
|
|
||||||
badges.unreadText.isVisible = true
|
|
||||||
badges.unreadText.text = chapterInfo.chapterCount.toString()
|
|
||||||
|
|
||||||
if (chapterInfo.latestChapter != null && chapterInfo.latestChapter > 0f) {
|
|
||||||
mangaLastChapterLabel.text = itemView.context.getString(
|
|
||||||
R.string.latest_,
|
|
||||||
DecimalFormat("#.#").format(chapterInfo.latestChapter),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mangaLastChapterLabel.text = itemView.context.getString(
|
|
||||||
R.string.latest_,
|
|
||||||
root.context.getString(R.string.unknown),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showPopupMenu(view: View) {
|
|
||||||
val item = adapter.getItem(bindingAdapterPosition) ?: return
|
|
||||||
|
|
||||||
// Create a PopupMenu, giving it the clicked view for an anchor
|
|
||||||
val popup = PopupMenu(view.context, view)
|
|
||||||
|
|
||||||
// Inflate our menu resource into the PopupMenu's Menu
|
|
||||||
popup.menuInflater.inflate(R.menu.migration_single, popup.menu)
|
|
||||||
|
|
||||||
val mangas = item.manga
|
|
||||||
|
|
||||||
popup.menu.findItem(R.id.action_search_manually).isVisible = true
|
|
||||||
// Hide download and show delete if the chapter is downloaded
|
|
||||||
if (mangas.searchResult.value != SearchResult.Searching) {
|
|
||||||
popup.menu.findItem(R.id.action_migrate_now).isVisible = true
|
|
||||||
popup.menu.findItem(R.id.action_copy_now).isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
|
||||||
adapter.controller.onMenuItemClick(item.manga.manga.id, menuItem)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally show the PopupMenu
|
|
||||||
popup.show()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
|
|
||||||
class MigrationProcessItem(val manga: MigratingManga) :
|
|
||||||
AbstractFlexibleItem<MigrationProcessHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
|
||||||
return R.layout.migration_process_item
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationProcessHolder {
|
|
||||||
return MigrationProcessHolder(view, adapter as MigrationProcessAdapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(
|
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
|
||||||
holder: MigrationProcessHolder,
|
|
||||||
position: Int,
|
|
||||||
payloads: MutableList<Any?>?,
|
|
||||||
) {
|
|
||||||
holder.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other is MigrationProcessItem) {
|
|
||||||
return manga.manga.id == other.manga.manga.id
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return manga.manga.id.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user