diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt index 9c92a7d18..5ebc38bfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt @@ -11,7 +11,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.chrisbanes.insetter.applyInsetter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.LatestControllerBinding +import eu.kanade.tachiyomi.databinding.GlobalSearchControllerBinding import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction @@ -28,7 +28,7 @@ import exh.savedsearches.models.SavedSearch * [FeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search */ open class FeedController : - NucleusController(), + NucleusController(), FeedCardAdapter.OnMangaClickListener, FeedAdapter.OnFeedClickListener { @@ -123,7 +123,7 @@ open class FeedController : onMangaClick(manga) } - override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater): GlobalSearchControllerBinding = GlobalSearchControllerBinding.inflate(inflater) /** * Called when the view is created diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedHolder.kt index a9d5c3786..6055a7f47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedHolder.kt @@ -6,7 +6,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.LatestControllerCardBinding +import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardBinding import eu.kanade.tachiyomi.util.system.LocaleHelper /** @@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper class FeedHolder(view: View, val adapter: FeedAdapter) : FlexibleViewHolder(view, adapter) { - private val binding = LatestControllerCardBinding.bind(view) + private val binding = GlobalSearchControllerCardBinding.bind(view) /** * Adapter containing manga from search results. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index f829b048b..ead822ee4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -345,7 +345,7 @@ open class BrowseSourcePresenter( .forEach { service -> launchIO { try { - service.match(source, manga)?.let { track -> + service.match(manga)?.let { track -> track.manga_id = manga.id!! (service as TrackService).bind(track) db.insertTrack(track).executeAsBlocking() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt index dc676368e..99a017606 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt @@ -13,7 +13,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu import dev.chrisbanes.insetter.applyInsetter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.LatestControllerBinding +import eu.kanade.tachiyomi.databinding.GlobalSearchControllerBinding import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.FilterList @@ -40,7 +40,7 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer * [SourceFeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search */ open class SourceFeedController : - SearchableNucleusController, + SearchableNucleusController, FabController, SourceFeedCardAdapter.OnMangaClickListener, SourceFeedAdapter.OnFeedClickListener { @@ -131,7 +131,7 @@ open class SourceFeedController : } } - override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater): GlobalSearchControllerBinding = GlobalSearchControllerBinding.inflate(inflater) /** * Called when the view is created diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedHolder.kt index 2ab83940b..ed0d8187d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedHolder.kt @@ -7,7 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.LatestControllerCardBinding +import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardBinding /** * Holder that binds the [SourceFeedItem] containing catalogue cards. @@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.databinding.LatestControllerCardBinding class SourceFeedHolder(view: View, val adapter: SourceFeedAdapter) : FlexibleViewHolder(view, adapter) { - private val binding = LatestControllerCardBinding.bind(view) + private val binding = GlobalSearchControllerCardBinding.bind(view) /** * Adapter containing manga from search results. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardAdapter.kt deleted file mode 100644 index 4f3964378..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardAdapter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.index - -import eu.davidea.flexibleadapter.FlexibleAdapter -import eu.kanade.tachiyomi.data.database.models.Manga - -/** - * Adapter that holds the manga items from search results. - * - * @param controller instance of [IndexController]. - */ -class IndexCardAdapter(controller: IndexController) : - FlexibleAdapter(null, controller, true) { - - /** - * Listen for browse item clicks. - */ - val mangaClickListener: OnMangaClickListener = controller - - /** - * Listener which should be called when user clicks browse. - * Note: Should only be handled by [IndexController] - */ - interface OnMangaClickListener { - fun onMangaClick(manga: Manga) - fun onMangaLongClick(manga: Manga) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt deleted file mode 100644 index 6ef2626d3..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt +++ /dev/null @@ -1,58 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.index - -import android.view.View -import androidx.core.view.isVisible -import coil.dispose -import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding -import eu.kanade.tachiyomi.util.view.loadAutoPause - -class IndexCardHolder(view: View, adapter: IndexCardAdapter) : - FlexibleViewHolder(view, adapter) { - - private val binding = GlobalSearchControllerCardItemBinding.bind(view) - - init { - // Call onMangaClickListener when item is pressed. - itemView.setOnClickListener { - val item = adapter.getItem(bindingAdapterPosition) - if (item != null) { - adapter.mangaClickListener.onMangaClick(item.manga) - } - } - itemView.setOnLongClickListener { - val item = adapter.getItem(bindingAdapterPosition) - if (item != null) { - adapter.mangaClickListener.onMangaLongClick(item.manga) - } - true - } - } - - fun bind(manga: Manga) { - binding.card.clipToOutline = true - - // Set manga title - binding.title.text = manga.title - - // Set alpha of thumbnail. - binding.cover.alpha = if (manga.favorite) 0.3f else 1.0f - - // For rounded corners - binding.badges.clipToOutline = true - - // Set favorite badge - binding.favoriteText.isVisible = manga.favorite - - setImage(manga) - } - - fun setImage(manga: Manga) { - binding.cover.dispose() - binding.cover.loadAutoPause(manga) { - setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardItem.kt deleted file mode 100644 index 35403cfe5..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardItem.kt +++ /dev/null @@ -1,40 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.index - -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 -import eu.kanade.tachiyomi.data.database.models.Manga - -class IndexCardItem(val manga: Manga) : AbstractFlexibleItem() { - - override fun getLayoutRes(): Int { - return R.layout.global_search_controller_card_item - } - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): IndexCardHolder { - return IndexCardHolder(view, adapter as IndexCardAdapter) - } - - override fun bindViewHolder( - adapter: FlexibleAdapter>, - holder: IndexCardHolder, - position: Int, - payloads: List? - ) { - holder.bind(manga) - } - - override fun equals(other: Any?): Boolean { - if (other is IndexCardItem) { - return manga.id == other.manga.id - } - return false - } - - override fun hashCode(): Int { - return manga.id?.toInt() ?: 0 - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexController.kt deleted file mode 100644 index 4a53b66e0..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexController.kt +++ /dev/null @@ -1,346 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.index - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.View -import androidx.core.os.bundleOf -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.databinding.IndexControllerBinding -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.ui.base.controller.FabController -import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet -import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController -import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.system.toast -import exh.util.nullIfBlank -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import reactivecircus.flowbinding.android.view.clicks -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import xyz.nulldev.ts.api.http.serializer.FilterSerializer - -/** - * This controller shows and manages the different search result in global search. - * This controller should only handle UI actions, IO actions should be done by [IndexPresenter] - * [IndexCardAdapter.OnMangaClickListener] called when manga is clicked in global search - */ -open class IndexController : - SearchableNucleusController, - FabController, - IndexCardAdapter.OnMangaClickListener { - - constructor(source: CatalogueSource?) : super( - bundleOf( - SOURCE_EXTRA to (source?.id ?: 0) - ) - ) { - this.source = source - } - - constructor(sourceId: Long) : this( - Injekt.get().get(sourceId) as? CatalogueSource - ) - - @Suppress("unused") - constructor(bundle: Bundle) : this(bundle.getLong(SOURCE_EXTRA)) - - var source: CatalogueSource? = null - - private var latestAdapter: IndexCardAdapter? = null - private var browseAdapter: IndexCardAdapter? = null - - private var actionFab: ExtendedFloatingActionButton? = null - - /** - * Sheet containing filter items. - */ - private var filterSheet: SourceFilterSheet? = null - - init { - setHasOptionsMenu(true) - } - - override fun getTitle(): String? { - return source!!.name - } - - /** - * Create the [IndexPresenter] used in controller. - * - * @return instance of [IndexPresenter] - */ - override fun createPresenter(): IndexPresenter { - return IndexPresenter(source!!) - } - - /** - * Called when manga in global search is clicked, opens manga. - * - * @param manga clicked item containing manga information. - */ - override fun onMangaClick(manga: Manga) { - // Open MangaController. - router.pushController(MangaController(manga, true).withFadeTransaction()) - } - - /** - * Called when manga in global search is long clicked. - * - * @param manga clicked item containing manga information. - */ - override fun onMangaLongClick(manga: Manga) { - // Delegate to single click by default. - onMangaClick(manga) - } - - /** - * Adds items to the options menu. - * - * @param menu menu containing options. - * @param inflater used to load the menu xml. - */ - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - createOptionsMenu(menu, inflater, R.menu.global_search, R.id.action_search) - } - - override fun onSearchViewQueryTextSubmit(query: String?) { - onBrowseClick(query.nullIfBlank()) - } - - override fun onSearchViewQueryTextChange(newText: String?) { - if (router.backstack.lastOrNull()?.controller == this) { - presenter.query = newText ?: "" - } - } - - override fun createBinding(inflater: LayoutInflater) = IndexControllerBinding.inflate(inflater) - - /** - * Called when the view is created - * - * @param view view of controller - */ - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - // Prepare filter sheet - initFilterSheet() - - latestAdapter = IndexCardAdapter(this) - - binding.latestRecycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false) - binding.latestRecycler.adapter = latestAdapter - - browseAdapter = IndexCardAdapter(this) - - binding.browseRecycler.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.HORIZONTAL, false) - binding.browseRecycler.adapter = browseAdapter - - binding.latestBarWrapper.clicks() - .onEach { - onLatestClick() - } - .launchIn(viewScope) - - binding.browseBarWrapper.clicks() - .onEach { - onBrowseClick() - } - .launchIn(viewScope) - - presenter.latestItems - .onEach { - bind(it, true) - } - .launchIn(viewScope) - - presenter.browseItems - .onEach { - bind(it, false) - } - .launchIn(viewScope) - - presenter.getLatest() - } - - private val filterSerializer = FilterSerializer() - - open fun initFilterSheet() { - if (presenter.sourceFilters.isEmpty()) { - actionFab?.text = activity!!.getString(R.string.saved_searches) - } - - filterSheet = SourceFilterSheet( - activity!!, - // SY --> - this, - presenter.source, - presenter.loadSearches(), - // SY <-- - onFilterClicked = { - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - filterSheet?.dismiss() - if (allDefault) { - onBrowseClick( - presenter.query.nullIfBlank() - ) - } else { - onBrowseClick( - presenter.query.nullIfBlank(), - filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters)) - ) - } - }, - onResetClicked = {}, - onSaveClicked = {}, - onSavedSearchClicked = cb@{ idOfSearch -> - val search = presenter.loadSearch(idOfSearch) - - if (search == null) { - filterSheet?.context?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search_failed_to_load) - .setMessage(R.string.save_search_failed_to_load_message) - .show() - } - return@cb - } - - if (search.filterList == null) { - activity?.toast(R.string.save_search_invalid) - return@cb - } - - presenter.sourceFilters = FilterList(search.filterList) - filterSheet?.setFilters(presenter.filterItems) - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - filterSheet?.dismiss() - - if (!allDefault) { - onBrowseClick( - search = presenter.query.nullIfBlank(), - savedSearch = search.id - ) - } - }, - onSavedSearchDeleteClicked = { _, _ -> } - ) - filterSheet?.setFilters(presenter.filterItems) - - // TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly - filterSheet?.setOnShowListener { actionFab?.isVisible = false } - filterSheet?.setOnDismissListener { actionFab?.isVisible = true } - - actionFab?.setOnClickListener { filterSheet?.show() } - - actionFab?.isVisible = true - } - - override fun configureFab(fab: ExtendedFloatingActionButton) { - actionFab = fab - - // Controlled by initFilterSheet() - fab.isVisible = false - - fab.setText(R.string.action_filter) - fab.setIconResource(R.drawable.ic_filter_list_24dp) - } - - override fun cleanupFab(fab: ExtendedFloatingActionButton) { - fab.setOnClickListener(null) - actionFab = null - } - - private fun bind(results: List?, isLatest: Boolean) { - val progress = if (isLatest) binding.latestProgress else binding.browseProgress - when { - results == null -> { - progress.isVisible = true - showResultsHolder(isLatest) - } - results.isEmpty() -> { - progress.isVisible = false - showNoResults(isLatest) - } - else -> { - progress.isVisible = false - showResultsHolder(isLatest) - } - } - - val adapter = if (isLatest) { - latestAdapter - } else { - browseAdapter - } - adapter?.updateDataSet(results) - } - - fun onError(e: Exception, isLatest: Boolean) { - e.message?.let { - val textView = if (isLatest) { - binding.latestNoResultsFound - } else { - binding.browseNoResultsFound - } - textView.text = it - } - } - - private fun showResultsHolder(isLatest: Boolean) { - (if (isLatest) binding.latestNoResultsFound else binding.browseNoResultsFound).isVisible = false - } - - private fun showNoResults(isLatest: Boolean) { - (if (isLatest) binding.latestNoResultsFound else binding.browseNoResultsFound).isVisible = true - } - - override fun onDestroyView(view: View) { - latestAdapter = null - browseAdapter = null - super.onDestroyView(view) - } - - fun onBrowseClick(search: String? = null, savedSearch: Long? = null, filters: String? = null) { - router.replaceTopController(BrowseSourceController(presenter.source, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction()) - } - - private fun onLatestClick() { - router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction()) - } - - /** - * Called from the presenter when a manga is initialized. - * - * @param manga the initialized manga. - */ - fun onMangaInitialized(manga: Manga, isLatest: Boolean) { - val adapter = if (isLatest) latestAdapter else browseAdapter - adapter ?: return - - adapter.allBoundViewHolders.forEach { - if (it !is IndexCardHolder) return@forEach - if (adapter.getItem(it.bindingAdapterPosition)?.manga?.id != manga.id) return@forEach - it.setImage(manga) - } - } - - companion object { - const val SOURCE_EXTRA = "source" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexPresenter.kt deleted file mode 100644 index 142ad2d5c..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexPresenter.kt +++ /dev/null @@ -1,274 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.index - -import android.os.Bundle -import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.toMangaInfo -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.model.toSManga -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems -import eu.kanade.tachiyomi.util.lang.awaitSingle -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withUIContext -import eu.kanade.tachiyomi.util.system.logcat -import exh.log.xLogE -import exh.savedsearches.EXHSavedSearch -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.buffer -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapConcat -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import logcat.LogPriority -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import xyz.nulldev.ts.api.http.serializer.FilterSerializer -import java.lang.RuntimeException - -/** - * Presenter of [IndexController] - * Function calls should be done from here. UI calls should be done from the controller. - * - * @param source the source. - * @param db manages the database calls. - * @param preferences manages the preference calls. - */ -open class IndexPresenter( - val source: CatalogueSource, - val db: DatabaseHelper = Injekt.get(), - val preferences: PreferencesHelper = Injekt.get() -) : BasePresenter() { - - /** - * Subject which fetches image of given manga. - */ - private val fetchImageFlow = MutableSharedFlow, Boolean>>() - - /** - * Modifiable list of filters. - */ - var sourceFilters = FilterList() - set(value) { - field = value - filterItems = value.toItems() - } - - var filterItems: List> = emptyList() - - /** - * Subscription for fetching images of manga. - */ - private var fetchImageJob: Job? = null - - val latestItems = MutableStateFlow?>(null) - - val browseItems = MutableStateFlow?>(null) - - init { - query = "" - } - - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - - sourceFilters = source.getFilterList() - } - - /** - * Initiates get latest per watching source. - */ - fun getLatest() { - // Create image fetch subscription - initializeFetchImageSubscription() - - presenterScope.launch(Dispatchers.IO) { - if (latestItems.value != null) return@launch - val results = if (source.supportsLatest) { - try { - source.fetchLatestUpdates(1) - .awaitSingle() - .mangas - .map { networkToLocalManga(it, source.id) } - } catch (e: Exception) { - withUIContext { - view?.onError(e, true) - } - emptyList() - } - } else emptyList() - - fetchImage(results, true) - - latestItems.value = results.map { IndexCardItem(it) } - } - - presenterScope.launch(Dispatchers.IO) { - if (browseItems.value != null) return@launch - val results = try { - source.fetchPopularManga(1) - .awaitSingle() - .mangas - .map { networkToLocalManga(it, source.id) } - } catch (e: Exception) { - withUIContext { - view?.onError(e, true) - } - emptyList() - } - - fetchImage(results, false) - - browseItems.value = results.map { IndexCardItem(it) } - } - } - - /** - * Initialize a list of manga. - * - * @param manga the list of manga to initialize. - */ - private fun fetchImage(manga: List, isLatest: Boolean) { - presenterScope.launchIO { - fetchImageFlow.emit(manga to isLatest) - } - } - - /** - * Subscribes to the initializer of manga details and updates the view if needed. - */ - private fun initializeFetchImageSubscription() { - fetchImageJob?.cancel() - fetchImageFlow - .flatMapConcat { (manga, isLatest) -> - manga.asFlow() - .filter { it.thumbnail_url == null && !it.initialized } - .map { - getMangaDetailsFlow(it, source, isLatest) - } - } - .buffer(Channel.RENDEZVOUS) - .flowOn(Dispatchers.IO) - .onEach { (manga, isLatest) -> - withUIContext { - view?.onMangaInitialized(manga, isLatest) - } - } - .catch { - logcat(LogPriority.ERROR, it) - } - .launchIn(presenterScope) - } - - /** - * Returns an observable of manga that initializes the given manga. - * - * @param manga the manga to initialize. - * @return an observable of the manga to initialize - */ - private suspend fun getMangaDetailsFlow(manga: Manga, source: Source, isLatest: Boolean): Pair { - val networkManga = source.getMangaDetails(manga.toMangaInfo()) - manga.copyFrom(networkManga.toSManga()) - manga.initialized = true - db.insertManga(manga).executeAsBlocking() - return manga to isLatest - } - - /** - * Returns a manga from the database for the given manga from network. It creates a new entry - * if the manga is not yet in the database. - * - * @param sManga the manga from the source. - * @return a manga from the database. - */ - protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { - var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking() - if (localManga == null) { - val newManga = Manga.create(sManga.url, sManga.title, sourceId) - newManga.copyFrom(sManga) - val result = db.insertManga(newManga).executeAsBlocking() - newManga.id = result.insertedId() - localManga = newManga - } - return localManga - } - - private val filterSerializer = FilterSerializer() - - fun loadSearch(searchId: Long): EXHSavedSearch? { - val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null - return EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null - ) - originalFilters - }.getOrNull() - ) - } - - fun loadSearches(): List { - return db.getSavedSearches(source.id).executeAsBlocking().map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null - ) - - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null - ) - } - } - } -} diff --git a/app/src/main/res/layout/index_controller.xml b/app/src/main/res/layout/index_controller.xml deleted file mode 100644 index 10ee230b1..000000000 --- a/app/src/main/res/layout/index_controller.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/latest_controller.xml b/app/src/main/res/layout/latest_controller.xml deleted file mode 100644 index a7e20b891..000000000 --- a/app/src/main/res/layout/latest_controller.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/latest_controller_card.xml b/app/src/main/res/layout/latest_controller_card.xml deleted file mode 100644 index 6aeb3d172..000000000 --- a/app/src/main/res/layout/latest_controller_card.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_sy.xml b/app/src/main/res/values-fr/strings_sy.xml index 1fc3fae12..a3d5b01a7 100644 --- a/app/src/main/res/values-fr/strings_sy.xml +++ b/app/src/main/res/values-fr/strings_sy.xml @@ -179,8 +179,6 @@ Lancer des mises à jour de catégorie tout le temps - Dernière position de l\'onglet - Voulez-vous que le dernier onglet soit le premier onglet de la navigation? Cela en fera l\'onglet par défaut lors de l\'ouverture de la navigation, non recommandé si vous êtes sur des données ou un réseau mesuré Remplacer le dernier bouton Remplacer le dernier bouton par une vue de navigation personnalisée qui inclut à la fois la dernière et la navigation Dossiers cachés de la source locale @@ -267,12 +265,6 @@ Aucune catégorie de source disponible Nom de catégorie non valide - - Regarder - Non regarder - Trop de sources regardées, impossible d\'en ajouter plus de 5 - Vous n\'avez pas de sources regardées, allez dans l\'onglet sources et appuyez longuement sur une source pour la regarder - Tag de tri des Tags Tri des tags diff --git a/app/src/main/res/values-in/strings_sy.xml b/app/src/main/res/values-in/strings_sy.xml index 62edcb4ca..286d350b8 100644 --- a/app/src/main/res/values-in/strings_sy.xml +++ b/app/src/main/res/values-in/strings_sy.xml @@ -192,8 +192,6 @@ Hanya jalankan pembaruan kategori - Posisi tab Terbaru - Apakah Anda ingin tab Terbaru menjadi tab pertama dalam penelusuran? Ini akan menjadikannya tab bawaan saat membuka jelajah, tidak disarankan jika Anda menggunakan data atau sedang menghemat data Filter Sumber dalam kategori Saring sumber-sumber yang ada dalam kategori, agar sumber-sumber tersebut tidak dimasukkan ke dalam kategori bahasa jika berada dalam suatu kategori Ganti tombol Terbaru @@ -359,12 +357,6 @@ Tak ada kategori sumber yang tersedia Nama kategori tidak valid - - Tonton - Berhenti tonton - Terlalu banyak sumber yang ditonton, batasnya adalah 5 - Kamu tidak memiliki sumber yang ditonton, buka tab sumber dan tekan lama sumber untuk menontonnya - Penyortir Tagar Menyortir tagar diff --git a/app/src/main/res/values-pt-rBR/strings_sy.xml b/app/src/main/res/values-pt-rBR/strings_sy.xml index ad9575c3f..a6d4d3189 100644 --- a/app/src/main/res/values-pt-rBR/strings_sy.xml +++ b/app/src/main/res/values-pt-rBR/strings_sy.xml @@ -188,8 +188,6 @@ Iniciar atualizações de categoria o tempo todo - Posição da aba Recentes - Deseja que a aba Recentes seja a primeira em Navegar? Isto a fará aba padrão ao abrir em Navegar, não recomendado se usa dados móveis ou uma rede medida. Filtrar fontes nas categorias Filtre as fontes que estão em categorias, evitando de pô-las sob seu idioma se estiverem numa categoria Substituir o botão Recentes @@ -330,12 +328,6 @@ Sem categorias de fonte disponíveis Nome de categoria inválido - - Seguir - Deixar de seguir - Muitas fontes seguidas, não pode seguir mais de 5 - Você não tem fontes seguidas, vá à aba Fontes e pressione e segure uma fonte para seguí-la - Tags de ordenação de tag Ordenação de tag diff --git a/app/src/main/res/values-ru/strings_sy.xml b/app/src/main/res/values-ru/strings_sy.xml index ca1d00651..bd2155e46 100644 --- a/app/src/main/res/values-ru/strings_sy.xml +++ b/app/src/main/res/values-ru/strings_sy.xml @@ -191,8 +191,6 @@ Постоянно запускать обновления категорий - Позиция вкладки (Последняя) - Поместить вкладку «Последняя» на место первой вкладки в «Поисковик»? Это сделает её вкладкой по умолчанию при открытии «Поисковик», не рекомендуется, если вы используете ограниченные сети(Моб. Данные) Фильтровать источники по категориям Фильтровать источники которые находятся в категориях, чтобы источники которые находятся в категориях, не попадали под языковую фильтрацию Убрать кнопку (Последняя) @@ -358,12 +356,6 @@ Отсутствуют категории для источников Недопустимое имя категории! - - Отслеживать - Не отслеживать - Слишком много отслеживаемых источников, нельзя добавить больше пяти - Нет отслеживаемых источников для отображения, перейдите во вкладку «Источники», после, нажмите на источник и удерживайте, затем выберите «Отслеживать». - Сортировка тэгов Сортировка тэгов