From 4e444a0408b031e44690438593c2c6b1a4098d51 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Wed, 7 Sep 2022 21:18:51 -0400 Subject: [PATCH] Convert metadata view to compose --- .../exh/ui/metadata/MetadataViewAdapter.kt | 48 -------- .../exh/ui/metadata/MetadataViewController.kt | 112 ++++++++++++------ .../exh/ui/metadata/MetadataViewPresenter.kt | 41 ++++--- 3 files changed, 97 insertions(+), 104 deletions(-) delete mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt b/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt deleted file mode 100644 index 389b26b36..000000000 --- a/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt +++ /dev/null @@ -1,48 +0,0 @@ -package exh.ui.metadata - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import eu.kanade.tachiyomi.databinding.MetadataViewItemBinding -import eu.kanade.tachiyomi.util.system.copyToClipboard - -class MetadataViewAdapter : - RecyclerView.Adapter() { - - private lateinit var binding: MetadataViewItemBinding - - var items: List> = emptyList() - set(value) { - if (field !== value) { - field = value - notifyDataSetChanged() - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MetadataViewAdapter.ViewHolder { - binding = MetadataViewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ViewHolder(binding.root) - } - - // binds the data to the TextView in each cell - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bind(items[position].first, items[position].second) - } - - // total number of cells - override fun getItemCount(): Int { - return items.size - } - - // stores and recycles views as they are scrolled off screen - inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - fun bind(title: String, text: String) { - binding.infoTitle.text = title - binding.infoText.text = text - binding.infoText.setOnClickListener { - itemView.context.copyToClipboard(title, text) - } - } - } -} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt index 1c343862d..598d455fc 100644 --- a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt +++ b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt @@ -1,24 +1,46 @@ package exh.ui.metadata import android.os.Bundle -import android.view.LayoutInflater -import android.view.View +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf -import androidx.recyclerview.widget.LinearLayoutManager -import dev.chrisbanes.insetter.applyInsetter import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.components.ScrollbarLazyColumn +import eu.kanade.presentation.util.clickableNoIndication +import eu.kanade.presentation.util.plus +import eu.kanade.presentation.util.topPaddingValues +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.ui.base.controller.FullComposeController import eu.kanade.tachiyomi.ui.manga.MangaController -import exh.metadata.metadata.base.RaisedSearchMetadata +import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class MetadataViewController : NucleusController { +class MetadataViewController : FullComposeController { constructor(manga: Manga) : super( bundleOf( MangaController.MANGA_EXTRA to manga.id, @@ -35,45 +57,59 @@ class MetadataViewController : NucleusController + AppBar( + title = manga?.title, + navigateUp = router::popCurrentController, + scrollBehavior = scrollBehavior, + ) + }, + ) { paddingValues -> + when (val state = state) { + MetadataViewState.Loading -> LoadingScreen() + MetadataViewState.MetadataNotFound -> EmptyScreen(R.string.no_results_found) + MetadataViewState.SourceNotFound -> EmptyScreen(R.string.source_empty_screen) + is MetadataViewState.Success -> { + val context = LocalContext.current + val items by derivedStateOf { state.meta.getExtraInfoPairs(context) } + ScrollbarLazyColumn( + contentPadding = paddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, + ) { + items(items) { (title, text) -> + Row( + Modifier.fillMaxWidth() + .clickableNoIndication(onLongClick = { context.copyToClipboard(title, text) }, onClick = {}) + .padding(vertical = 8.dp), + ) { + Text( + title, + modifier = Modifier.width(140.dp).padding(start = 16.dp), + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text, + modifier = Modifier.fillMaxWidth().padding(start = 8.dp, end = 8.dp), + style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = 0.7F), + ) + } + } + } + } } } - - if (manga == null || source == null) return - binding.recycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) - adapter = MetadataViewAdapter() - binding.recycler.adapter = adapter - binding.recycler.setHasFixedSize(true) - } - - fun onNextMangaInfo(meta: RaisedSearchMetadata?) { - val adapter = adapter ?: return - val context = view?.context ?: return - adapter.items = meta?.getExtraInfoPairs(context).orEmpty() } } diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt b/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt index 2aad9c6eb..f80acd028 100644 --- a/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt +++ b/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt @@ -3,41 +3,46 @@ package exh.ui.metadata import android.os.Bundle import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.MetadataSource -import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.util.lang.launchNonCancellableIO import exh.metadata.metadata.base.RaisedSearchMetadata import exh.source.getMainSource -import exh.ui.base.CoroutinePresenter import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class MetadataViewPresenter( val manga: Manga, val source: Source, - val preferences: PreferencesHelper = Injekt.get(), private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(), -) : CoroutinePresenter() { - - val meta = MutableStateFlow(null) +) : BasePresenter() { + private val _state = MutableStateFlow(MetadataViewState.Loading) + val state = _state.asStateFlow() override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - launchIO { - val flatMetadata = getFlatMetadataById.await(manga.id) ?: return@launchIO - val mainSource = source.getMainSource>() - if (mainSource != null) { - meta.value = flatMetadata.raise(mainSource.metaClass) + presenterScope.launchNonCancellableIO { + val metadataSource = source.getMainSource>() + if (metadataSource == null) { + _state.value = MetadataViewState.SourceNotFound + return@launchNonCancellableIO + } + + _state.value = when (val flatMetadata = getFlatMetadataById.await(manga.id)) { + null -> MetadataViewState.MetadataNotFound + else -> MetadataViewState.Success(flatMetadata.raise(metadataSource.metaClass)) } } - - meta - .inView { view, metadata -> - view.onNextMangaInfo(metadata) - } - .launch() } } + +sealed class MetadataViewState { + object Loading : MetadataViewState() + data class Success(val meta: RaisedSearchMetadata) : MetadataViewState() + object MetadataNotFound : MetadataViewState() + object SourceNotFound : MetadataViewState() +}