Merge branch 'devel' into feature/sync
commit
00e1aac984
@ -1,9 +1,9 @@
|
|||||||
package org.koitharu.kotatsu.favourites.ui
|
package org.koitharu.kotatsu.favourites.ui
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||||
|
|
||||||
fun interface FavouritesTabLongClickListener {
|
fun interface FavouritesTabLongClickListener {
|
||||||
|
|
||||||
fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean
|
fun onTabLongClick(tabView: View, item: CategoryListModel): Boolean
|
||||||
}
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package org.koitharu.kotatsu.favourites.ui.categories
|
||||||
|
|
||||||
|
interface AllCategoriesToggleListener {
|
||||||
|
|
||||||
|
fun onAllCategoriesToggle(isVisible: Boolean)
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding
|
||||||
|
import org.koitharu.kotatsu.favourites.ui.categories.AllCategoriesToggleListener
|
||||||
|
|
||||||
|
fun allCategoriesAD(
|
||||||
|
allCategoriesToggleListener: AllCategoriesToggleListener,
|
||||||
|
) = adapterDelegateViewBinding<CategoryListModel.All, CategoryListModel, ItemCategoriesAllBinding>(
|
||||||
|
{ inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
binding.imageViewToggle.setOnClickListener {
|
||||||
|
allCategoriesToggleListener.onAllCategoriesToggle(!item.isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.imageViewToggle.isChecked = item.isVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
|
||||||
|
sealed interface CategoryListModel : ListModel {
|
||||||
|
|
||||||
|
val id: Long
|
||||||
|
|
||||||
|
class All(
|
||||||
|
val isVisible: Boolean,
|
||||||
|
) : CategoryListModel {
|
||||||
|
|
||||||
|
override val id: Long = 0L
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as All
|
||||||
|
|
||||||
|
if (isVisible != other.isVisible) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return isVisible.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryItem(
|
||||||
|
val category: FavouriteCategory,
|
||||||
|
) : CategoryListModel {
|
||||||
|
|
||||||
|
override val id: Long
|
||||||
|
get() = category.id
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CategoryItem
|
||||||
|
|
||||||
|
if (category.id != other.category.id) return false
|
||||||
|
if (category.title != other.category.title) return false
|
||||||
|
if (category.order != other.category.order) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = category.id.hashCode()
|
||||||
|
result = 31 * result + category.title.hashCode()
|
||||||
|
result = 31 * result + category.order.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package org.koitharu.kotatsu.reader.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okio.IOException
|
||||||
|
import org.koitharu.kotatsu.local.data.PagesCache
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
class PageSaveHelper(
|
||||||
|
private val cache: PagesCache,
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var continuation: Continuation<Uri>? = null
|
||||||
|
private val contentResolver = context.contentResolver
|
||||||
|
|
||||||
|
suspend fun savePage(
|
||||||
|
pageLoader: PageLoader,
|
||||||
|
page: MangaPage,
|
||||||
|
saveLauncher: ActivityResultLauncher<String>,
|
||||||
|
): Uri {
|
||||||
|
var pageFile = cache[page.url]
|
||||||
|
var fileName = pageFile?.name
|
||||||
|
if (fileName == null) {
|
||||||
|
fileName = pageLoader.getPageUrl(page).toHttpUrl().pathSegments.last()
|
||||||
|
}
|
||||||
|
val cc = coroutineContext
|
||||||
|
val destination = suspendCancellableCoroutine<Uri> { cont ->
|
||||||
|
continuation = cont
|
||||||
|
Dispatchers.Main.dispatch(cc) {
|
||||||
|
saveLauncher.launch(fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continuation = null
|
||||||
|
if (pageFile == null) {
|
||||||
|
pageFile = pageLoader.loadPage(page, force = false)
|
||||||
|
}
|
||||||
|
runInterruptible(Dispatchers.IO) {
|
||||||
|
contentResolver.openOutputStream(destination)?.use { output ->
|
||||||
|
pageFile.inputStream().use { input ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
} ?: throw IOException("Output stream is null")
|
||||||
|
}
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onActivityResult(uri: Uri): Boolean = continuation?.apply {
|
||||||
|
resume(uri)
|
||||||
|
} != null
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.newsources
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
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.AlertDialogFragment
|
||||||
|
import org.koitharu.kotatsu.databinding.DialogOnboardBinding
|
||||||
|
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter
|
||||||
|
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
|
||||||
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||||
|
|
||||||
|
class NewSourcesDialogFragment :
|
||||||
|
AlertDialogFragment<DialogOnboardBinding>(),
|
||||||
|
SourceConfigListener,
|
||||||
|
DialogInterface.OnClickListener {
|
||||||
|
|
||||||
|
private val viewModel by viewModel<NewSourcesViewModel>()
|
||||||
|
|
||||||
|
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): DialogOnboardBinding {
|
||||||
|
return DialogOnboardBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val adapter = SourceConfigAdapter(this, get(), viewLifecycleOwner)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
binding.textViewTitle.setText(R.string.new_sources_text)
|
||||||
|
|
||||||
|
viewModel.sources.observe(viewLifecycleOwner) { adapter.items = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBuildDialog(builder: MaterialAlertDialogBuilder) {
|
||||||
|
builder
|
||||||
|
.setPositiveButton(R.string.done, this)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setTitle(R.string.remote_sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
|
viewModel.apply()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSettingsClick(item: SourceConfigItem.SourceItem) = Unit
|
||||||
|
|
||||||
|
override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||||
|
viewModel.onItemEnabledChanged(item, isEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDragHandleTouch(holder: RecyclerView.ViewHolder) = Unit
|
||||||
|
|
||||||
|
override fun onHeaderClick(header: SourceConfigItem.LocaleGroup) = Unit
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TAG = "NewSources"
|
||||||
|
|
||||||
|
fun show(fm: FragmentManager) = NewSourcesDialogFragment().show(fm, TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.newsources
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||||
|
|
||||||
|
class NewSourcesViewModel(
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val sources = MutableLiveData<List<SourceConfigItem>>()
|
||||||
|
private val initialList = settings.newSources
|
||||||
|
|
||||||
|
init {
|
||||||
|
buildList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||||
|
if (isEnabled) {
|
||||||
|
settings.hiddenSources -= item.source.name
|
||||||
|
} else {
|
||||||
|
settings.hiddenSources += item.source.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun apply() {
|
||||||
|
settings.markKnownSources(initialList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildList() {
|
||||||
|
val hidden = settings.hiddenSources
|
||||||
|
sources.value = initialList.map {
|
||||||
|
SourceConfigItem.SourceItem(
|
||||||
|
source = it,
|
||||||
|
summary = null,
|
||||||
|
isEnabled = it.name !in hidden,
|
||||||
|
isDraggable = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
class CompositeMutex<T : Any> : Set<T> {
|
||||||
|
|
||||||
|
private val data = HashMap<T, MutableList<CancellableContinuation<Unit>>>()
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = data.size
|
||||||
|
|
||||||
|
override fun contains(element: T): Boolean {
|
||||||
|
return data.containsKey(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun containsAll(elements: Collection<T>): Boolean {
|
||||||
|
return elements.all { x -> data.containsKey(x) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEmpty(): Boolean {
|
||||||
|
return data.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> {
|
||||||
|
return data.keys.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun lock(element: T) {
|
||||||
|
waitForRemoval(element)
|
||||||
|
mutex.withLock {
|
||||||
|
val lastValue = data.put(element, LinkedList<CancellableContinuation<Unit>>())
|
||||||
|
check(lastValue == null) {
|
||||||
|
"CompositeMutex is double-locked for $element"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unlock(element: T) {
|
||||||
|
val continuations = mutex.withLock {
|
||||||
|
checkNotNull(data.remove(element)) {
|
||||||
|
"CompositeMutex is not locked for $element"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continuations.forEach { c ->
|
||||||
|
if (c.isActive) {
|
||||||
|
c.resume(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun waitForRemoval(element: T) {
|
||||||
|
val list = data[element] ?: return
|
||||||
|
suspendCancellableCoroutine<Unit> { continuation ->
|
||||||
|
list.add(continuation)
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
list.remove(continuation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,26 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import okio.IOException
|
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
|
||||||
|
|
||||||
class ExternalStorageHelper(context: Context) {
|
|
||||||
|
|
||||||
private val contentResolver = context.contentResolver
|
|
||||||
|
|
||||||
suspend fun savePage(page: MangaPage, destination: Uri) {
|
|
||||||
val pageLoader = PageLoader()
|
|
||||||
val pageFile = pageLoader.loadPage(page, force = false)
|
|
||||||
runInterruptible(Dispatchers.IO) {
|
|
||||||
contentResolver.openOutputStream(destination)?.use { output ->
|
|
||||||
pageFile.inputStream().use { input ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
} ?: throw IOException("Output stream is null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z" />
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.12950581"
|
||||||
|
android:scaleY="0.12950581"
|
||||||
|
android:translateX="20.846512"
|
||||||
|
android:translateY="20.846512">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="m256,206c-50.54,0 -91.67,44.86 -91.67,100 0,55.14 41.13,100 91.67,100 50.54,0 91.67,-44.86 91.67,-100 0,-55.14 -41.13,-100 -91.67,-100zM221.79,284.73c-1.46,2.91 -4.41,4.61 -7.45,4.61 -1.25,0 -2.52,-0.28 -3.73,-0.88l-12.94,-6.48 -12.94,6.48c-4.13,2.07 -9.11,0.37 -11.18,-3.73 -2.05,-4.12 -0.39,-9.11 3.73,-11.18l16.67,-8.33c2.34,-1.17 5.11,-1.17 7.45,0l16.67,8.33c4.12,2.07 5.78,7.06 3.73,11.18zM280.12,259.73c-1.46,2.91 -4.41,4.61 -7.45,4.61 -1.25,0 -2.52,-0.28 -3.73,-0.88l-12.94,-6.48 -12.94,6.48c-4.13,2.07 -9.11,0.37 -11.18,-3.73 -2.05,-4.12 -0.39,-9.11 3.73,-11.18l16.67,-8.33c2.34,-1.17 5.11,-1.17 7.45,0l16.67,8.33c4.12,2.07 5.78,7.06 3.73,11.18zM334.73,273.54c4.12,2.07 5.78,7.06 3.73,11.18 -1.46,2.91 -4.41,4.61 -7.45,4.61 -1.25,0 -2.52,-0.28 -3.73,-0.88l-12.94,-6.48 -12.94,6.48c-4.12,2.07 -9.11,0.37 -11.18,-3.73 -2.05,-4.12 -0.39,-9.11 3.73,-11.18l16.67,-8.33c2.34,-1.17 5.11,-1.17 7.45,0z"
|
||||||
|
android:strokeWidth="0.781247" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="m364.24,169.25c-3.48,-6.91 -6.92,-13.42 -10.21,-19.37l-8.27,-14.48c-6.8,-11.52 -12.26,-19.9 -14.78,-23.67 -0.72,-43.33 -19.12,-53.79 -21.26,-54.87 -3.21,-1.58 -7.1,-0.98 -9.62,1.56 -12.26,12.26 -20.23,24.46 -24.01,30.89h-40.2c-3.78,-6.43 -11.75,-18.64 -24.01,-30.89 -2.52,-2.54 -6.4,-3.14 -9.62,-1.56 -2.13,1.07 -20.54,11.54 -21.26,54.87 -2.53,3.76 -7.99,12.15 -14.78,23.66l-8.27,14.49c-3.29,5.96 -6.73,12.47 -10.21,19.38l-7.44,15.33c-17.73,38.05 -34.3,85.43 -34.3,129.73 0,69.32 58.27,128.42 60.76,130.89 0.91,0.91 2.03,1.61 3.26,2.02 1.07,0.36 26.79,8.76 69.32,8.76 2.21,0 4.33,-0.88 5.89,-2.44l5.89,-5.89h9.77l5.89,5.89c1.56,1.56 3.68,2.44 5.89,2.44 42.53,0 68.25,-8.4 69.32,-8.76 1.22,-0.41 2.34,-1.11 3.26,-2.02 2.49,-2.47 60.76,-61.57 60.76,-130.89 0,-44.3 -16.57,-91.67 -34.3,-129.73zM297.67,122.67c4.61,0 4.41,7.35 4.41,11.96 4.61,0 12.25,0.1 12.25,4.71 0,9.2 -7.47,16.67 -16.67,16.67 -9.2,0 -16.67,-7.47 -16.67,-16.67 0,-9.2 7.47,-16.67 16.67,-16.67zM239.97,152.81c1.29,-3.11 4.33,-5.14 7.7,-5.14h16.67c3.37,0 6.41,2.03 7.7,5.14 1.29,3.11 0.57,6.71 -1.81,9.08l-8.33,8.33c-1.63,1.63 -3.76,2.44 -5.89,2.44 -2.13,0 -4.26,-0.81 -5.89,-2.44l-8.33,-8.33c-2.37,-2.38 -3.09,-5.97 -1.8,-9.08zM214.33,122.67c4.61,0 4.41,7.35 4.41,11.96 4.61,0 12.25,0.1 12.25,4.71 0,9.2 -7.47,16.67 -16.67,16.67 -9.2,0 -16.67,-7.47 -16.67,-16.67 -0,-9.2 7.47,-16.67 16.67,-16.67zM256,422.67c-59.73,0 -108.33,-52.34 -108.33,-116.67 0,-64.32 48.6,-116.67 108.33,-116.67 59.73,0 108.33,52.34 108.33,116.67 0,64.32 -48.6,116.67 -108.33,116.67z"
|
||||||
|
android:strokeWidth="0.781247" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C21.27,16.39 17,19.5 12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C15.76,17.5 19.17,15.36 20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12Z" />
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_shown" android:state_checked="true" />
|
||||||
|
<item android:drawable="@drawable/ic_hidden" android:state_checked="false" />
|
||||||
|
</selector>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
tools:ignore="Overdraw">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/all_favourites"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.base.ui.widgets.CheckableImageView
|
||||||
|
android:id="@+id/imageView_toggle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:padding="?listPreferredItemPaddingEnd"
|
||||||
|
android:scaleType="center"
|
||||||
|
app:srcCompat="@drawable/ic_shown_hidden" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Loading…
Reference in New Issue