From 2ac6b84f871d32066e263276622b9deedc45794f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 25 Jan 2022 19:56:37 +0200 Subject: [PATCH] Show favicons in sources list --- .../kotatsu/core/parser/FaviconMapper.kt | 18 ++++++ .../core/parser/RemoteMangaRepository.kt | 2 +- .../core/parser/site/AnibelRepository.kt | 4 ++ .../org/koitharu/kotatsu/core/ui/uiModule.kt | 2 + .../koitharu/kotatsu/local/data/CbzFetcher.kt | 7 ++- .../sources/SourcesSettingsFragment.kt | 3 +- .../sources/SourcesSettingsViewModel.kt | 2 + .../sources/adapter/SourceConfigAdapter.kt | 7 ++- .../adapter/SourceConfigAdapterDelegates.kt | 55 +++++++++++++++++-- .../sources/model/SourceConfigItem.kt | 7 +++ .../main/res/drawable/ic_favicon_fallback.xml | 16 ++++++ .../main/res/layout/item_source_config.xml | 14 +++-- .../layout/item_source_config_draggable.xml | 46 ++++++++++++++++ 13 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt create mode 100644 app/src/main/res/drawable/ic_favicon_fallback.xml create mode 100644 app/src/main/res/layout/item_source_config_draggable.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt new file mode 100644 index 000000000..44f1ca040 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.core.parser + +import android.net.Uri +import coil.map.Mapper +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koitharu.kotatsu.core.model.MangaSource + +class FaviconMapper() : Mapper { + + override fun map(data: Uri): HttpUrl { + val mangaSource = MangaSource.valueOf(data.schemeSpecificPart) + val repo = MangaRepository(mangaSource) as RemoteMangaRepository + return repo.getFaviconUrl().toHttpUrl() + } + + override fun handles(data: Uri) = data.scheme == "favicon" +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 80321b87a..3505c53b8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -26,7 +26,7 @@ abstract class RemoteMangaRepository( override suspend fun getTags(): Set = emptySet() - fun getFaviconUrl() = "https://${getDomain()}/favicon.ico" + open fun getFaviconUrl() = "https://${getDomain()}/favicon.ico" open fun onCreatePreferences(map: MutableMap) { map[SourceSettings.KEY_DOMAIN] = defaultDomain diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt index 03853cd1f..e71378eec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt @@ -21,6 +21,10 @@ class AnibelRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor SortOrder.NEWEST ) + override fun getFaviconUrl(): String { + return "https://cdn.${getDomain()}/favicons/favicon.png" + } + override suspend fun getList2( offset: Int, query: String?, diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt index 8032d9783..148fa409d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt @@ -5,6 +5,7 @@ import coil.ImageLoader import okhttp3.OkHttpClient import org.koin.android.ext.koin.androidContext import org.koin.dsl.module +import org.koitharu.kotatsu.core.parser.FaviconMapper import org.koitharu.kotatsu.local.data.CbzFetcher val uiModule @@ -15,6 +16,7 @@ val uiModule .componentRegistry( ComponentRegistry.Builder() .add(CbzFetcher()) + .add(FaviconMapper()) .build() ).build() } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt index cd4f5ea83..4e2746cec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt @@ -9,23 +9,24 @@ import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.fetch.SourceResult import coil.size.Size +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import okio.buffer import okio.source import java.util.zip.ZipFile class CbzFetcher : Fetcher { - @Suppress("BlockingMethodInNonBlockingContext") override suspend fun fetch( pool: BitmapPool, data: Uri, size: Size, options: Options, - ): FetchResult { + ): FetchResult = runInterruptible(Dispatchers.IO) { val zip = ZipFile(data.schemeSpecificPart) val entry = zip.getEntry(data.fragment) val ext = MimeTypeMap.getFileExtensionFromUrl(entry.name) - return SourceResult( + SourceResult( source = ExtraCloseableBufferedSource( zip.getInputStream(entry).source().buffer(), zip, diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index 25893f670..53d165b92 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -8,6 +8,7 @@ import androidx.core.graphics.Insets import androidx.core.view.updatePadding import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment @@ -42,7 +43,7 @@ class SourcesSettingsFragment : BaseFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val sourcesAdapter = SourceConfigAdapter(this) + val sourcesAdapter = SourceConfigAdapter(this, get(), viewLifecycleOwner) with(binding.recyclerView) { setHasFixedSize(true) addItemDecoration(SourceConfigItemDecoration(view.context)) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt index 52125df63..1bff594c9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt @@ -81,6 +81,7 @@ class SourcesSettingsViewModel( SourceConfigItem.SourceItem( source = it, isEnabled = true, + isDraggable = true, ) } } @@ -102,6 +103,7 @@ class SourcesSettingsViewModel( SourceConfigItem.SourceItem( source = it, isEnabled = false, + isDraggable = false, ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt index d04d22fcc..79cab4f39 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt @@ -1,13 +1,18 @@ package org.koitharu.kotatsu.settings.sources.adapter +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem class SourceConfigAdapter( listener: SourceConfigListener, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, ) : AsyncListDifferDelegationAdapter( SourceConfigDiffCallback(), sourceConfigHeaderDelegate(), sourceConfigGroupDelegate(listener), - sourceConfigItemDelegate(listener), + sourceConfigItemDelegate(listener, coil, lifecycleOwner), + sourceConfigDraggableItemDelegate(listener), ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index df7435bac..df2b734b9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -6,12 +6,18 @@ import android.view.View import android.widget.CompoundButton import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader +import coil.request.Disposable +import coil.request.ImageRequest import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemExpandableBinding import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding +import org.koitharu.kotatsu.databinding.ItemSourceConfigDraggableBinding import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import org.koitharu.kotatsu.utils.ext.enqueueWith fun sourceConfigHeaderDelegate() = adapterDelegateViewBinding( { layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) } @@ -38,11 +44,54 @@ fun sourceConfigGroupDelegate( } } -@SuppressLint("ClickableViewAccessibility") fun sourceConfigItemDelegate( listener: SourceConfigListener, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) } + { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) }, + on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable } +) { + + val eventListener = object : View.OnClickListener, CompoundButton.OnCheckedChangeListener { + override fun onClick(v: View?) = listener.onItemSettingsClick(item) + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + listener.onItemEnabledChanged(item, isChecked) + } + } + var imageRequest: Disposable? = null + + binding.imageViewConfig.setOnClickListener(eventListener) + binding.switchToggle.setOnCheckedChangeListener(eventListener) + + bind { + binding.textViewTitle.text = item.source.title + binding.switchToggle.isChecked = item.isEnabled + binding.imageViewConfig.isVisible = item.isEnabled + binding.root.updatePaddingRelative( + end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, + ) + imageRequest = ImageRequest.Builder(context) + .data(item.faviconUrl) + .error(R.drawable.ic_favicon_fallback) + .target(binding.imageViewIcon) + .lifecycle(lifecycleOwner) + .enqueueWith(coil) + } + + onViewRecycled { + imageRequest?.dispose() + imageRequest = null + } +} + +@SuppressLint("ClickableViewAccessibility") +fun sourceConfigDraggableItemDelegate( + listener: SourceConfigListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) }, + on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable } ) { val eventListener = object : View.OnClickListener, View.OnTouchListener, @@ -70,10 +119,8 @@ fun sourceConfigItemDelegate( bind { binding.textViewTitle.text = item.source.title binding.switchToggle.isChecked = item.isEnabled - binding.imageViewHandle.isVisible = item.isEnabled binding.imageViewConfig.isVisible = item.isEnabled binding.root.updatePaddingRelative( - start = if (item.isEnabled) 0 else binding.imageViewHandle.paddingStart * 2, end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt index 965ea1171..a3e398a03 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.settings.sources.model +import android.net.Uri import androidx.annotation.StringRes import org.koitharu.kotatsu.core.model.MangaSource @@ -49,8 +50,12 @@ sealed interface SourceConfigItem { class SourceItem( val source: MangaSource, val isEnabled: Boolean, + val isDraggable: Boolean, ) : SourceConfigItem { + val faviconUrl: Uri + get() = Uri.fromParts("favicon", source.name, null) + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -59,6 +64,7 @@ sealed interface SourceConfigItem { if (source != other.source) return false if (isEnabled != other.isEnabled) return false + if (isDraggable != other.isDraggable) return false return true } @@ -66,6 +72,7 @@ sealed interface SourceConfigItem { override fun hashCode(): Int { var result = source.hashCode() result = 31 * result + isEnabled.hashCode() + result = 31 * result + isDraggable.hashCode() return result } } diff --git a/app/src/main/res/drawable/ic_favicon_fallback.xml b/app/src/main/res/drawable/ic_favicon_fallback.xml new file mode 100644 index 000000000..24996b554 --- /dev/null +++ b/app/src/main/res/drawable/ic_favicon_fallback.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index ffa9a68e5..2d225fea2 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -9,12 +9,14 @@ android:orientation="horizontal"> + android:id="@+id/imageView_icon" + android:layout_width="?android:listPreferredItemHeightSmall" + android:layout_height="?android:listPreferredItemHeightSmall" + android:layout_marginHorizontal="?listPreferredItemPaddingStart" + android:labelFor="@id/textView_title" + android:padding="6dp" + android:scaleType="fitCenter" + tools:src="@tools:sample/avatars" /> + + + + + + + + + + + \ No newline at end of file