Multiple sources selection

This commit is contained in:
Koitharu
2024-05-10 17:39:00 +03:00
parent 82684601b7
commit 7c82b4effb
16 changed files with 221 additions and 73 deletions

View File

@@ -18,6 +18,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.TABLE_HISTORY import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaDataRepository
@@ -150,7 +151,7 @@ class AppShortcutManager @Inject constructor(
.build() .build()
} }
private suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat { private suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat = withContext(Dispatchers.Default) {
val icon = runCatchingCancellable { val icon = runCatchingCancellable {
coil.execute( coil.execute(
ImageRequest.Builder(context) ImageRequest.Builder(context)
@@ -163,7 +164,7 @@ class AppShortcutManager @Inject constructor(
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) }, onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) }, onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
) )
return ShortcutInfoCompat.Builder(context, source.name) ShortcutInfoCompat.Builder(context, source.name)
.setShortLabel(source.title) .setShortLabel(source.title)
.setLongLabel(source.title) .setLongLabel(source.title)
.setIcon(icon) .setIcon(icon)

View File

@@ -7,6 +7,7 @@ import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.view.View import android.view.View
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -19,7 +20,10 @@ import com.google.android.material.R as materialR
class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() { class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val defaultRadius = context.resources.getDimension(materialR.dimen.abc_control_corner_material) private val radius = context.resources.getDimension(materialR.dimen.abc_control_corner_material)
private val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)
private val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_offset)
private val iconSize = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_size)
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED) private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
private val fillColor = ColorUtils.setAlphaComponent( private val fillColor = ColorUtils.setAlphaComponent(
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f), ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
@@ -32,11 +36,12 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
98, 98,
) )
paint.style = Paint.Style.FILL paint.style = Paint.Style.FILL
hasBackground = false hasBackground = true
hasForeground = true hasForeground = true
isIncludeDecorAndMargins = false isIncludeDecorAndMargins = false
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width) paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
checkIcon?.setTint(strokeColor)
} }
override fun getItemId(parent: RecyclerView, child: View): Long { override fun getItemId(parent: RecyclerView, child: View): Long {
@@ -45,6 +50,19 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
return item.chapter.id return item.chapter.id
} }
override fun onDrawBackground(
canvas: Canvas,
parent: RecyclerView,
child: View,
bounds: RectF,
state: RecyclerView.State,
) {
if (child is CardView) {
return
}
canvas.drawRoundRect(bounds, radius, radius, paint)
}
override fun onDrawForeground( override fun onDrawForeground(
canvas: Canvas, canvas: Canvas,
parent: RecyclerView, parent: RecyclerView,
@@ -52,16 +70,24 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
bounds: RectF, bounds: RectF,
state: RecyclerView.State state: RecyclerView.State
) { ) {
val radius = if (child is CardView) { if (child !is CardView) {
child.radius return
} else {
defaultRadius
} }
val radius = child.radius
paint.color = fillColor paint.color = fillColor
paint.style = Paint.Style.FILL paint.style = Paint.Style.FILL
canvas.drawRoundRect(bounds, radius, radius, paint) canvas.drawRoundRect(bounds, radius, radius, paint)
paint.color = strokeColor paint.color = strokeColor
paint.style = Paint.Style.STROKE paint.style = Paint.Style.STROKE
canvas.drawRoundRect(bounds, radius, radius, paint) canvas.drawRoundRect(bounds, radius, radius, paint)
checkIcon?.run {
setBounds(
(bounds.right - iconSize - iconOffset).toInt(),
(bounds.top + iconOffset).toInt(),
(bounds.right - iconOffset).toInt(),
(bounds.top + iconOffset + iconSize).toInt(),
)
draw(canvas)
}
} }
} }

View File

@@ -97,10 +97,10 @@ class MangaSourcesRepository @Inject constructor(
result result
} }
suspend fun setSourceEnabled(source: MangaSource, isEnabled: Boolean): ReversibleHandle { suspend fun setSourcesEnabled(sources: Collection<MangaSource>, isEnabled: Boolean): ReversibleHandle {
dao.setEnabled(source.name, isEnabled) setSourcesEnabledImpl(sources, isEnabled)
return ReversibleHandle { return ReversibleHandle {
dao.setEnabled(source.name, !isEnabled) setSourcesEnabledImpl(sources, !isEnabled)
} }
} }
@@ -171,6 +171,18 @@ class MangaSourcesRepository @Inject constructor(
return dao.findAll().isEmpty() return dao.findAll().isEmpty()
} }
private suspend fun setSourcesEnabledImpl(sources: Collection<MangaSource>, isEnabled: Boolean) {
if (sources.size == 1) { // fast path
dao.setEnabled(sources.first().name, isEnabled)
return
}
db.withTransaction {
for (source in sources) {
dao.setEnabled(source.name, isEnabled)
}
}
}
private suspend fun getNewSources(): MutableSet<MangaSource> { private suspend fun getNewSources(): MutableSet<MangaSource> {
val entities = dao.findAll() val entities = dao.findAll()
val result = EnumSet.copyOf(remoteSources) val result = EnumSet.copyOf(remoteSources)

View File

@@ -4,11 +4,11 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.view.ActionMode
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -24,12 +24,14 @@ import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver
import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
@@ -44,6 +46,7 @@ import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.TipModel import org.koitharu.kotatsu.list.ui.model.TipModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
@@ -56,7 +59,7 @@ class ExploreFragment :
BaseFragment<FragmentExploreBinding>(), BaseFragment<FragmentExploreBinding>(),
RecyclerViewOwner, RecyclerViewOwner,
ExploreListEventListener, ExploreListEventListener,
OnListItemClickListener<MangaSourceItem>, TipView.OnButtonClickListener { OnListItemClickListener<MangaSourceItem>, TipView.OnButtonClickListener, ListSelectionController.Callback2 {
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
@@ -66,6 +69,7 @@ class ExploreFragment :
private val viewModel by viewModels<ExploreViewModel>() private val viewModel by viewModels<ExploreViewModel>()
private var exploreAdapter: ExploreAdapter? = null private var exploreAdapter: ExploreAdapter? = null
private var sourceSelectionController: ListSelectionController? = null
override val recyclerView: RecyclerView override val recyclerView: RecyclerView
get() = requireViewBinding().recyclerView get() = requireViewBinding().recyclerView
@@ -79,11 +83,18 @@ class ExploreFragment :
exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this, this) { manga, view -> exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this, this) { manga, view ->
startActivity(DetailsActivity.newIntent(view.context, manga)) startActivity(DetailsActivity.newIntent(view.context, manga))
} }
sourceSelectionController = ListSelectionController(
appCompatDelegate = checkNotNull(findAppCompatDelegate()),
decoration = SourceSelectionDecoration(binding.root.context),
registryOwner = this,
callback = this,
)
with(binding.recyclerView) { with(binding.recyclerView) {
adapter = exploreAdapter adapter = exploreAdapter
setHasFixedSize(true) setHasFixedSize(true)
SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach() SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()
addItemDecoration(TypedListSpacingDecoration(context, false)) addItemDecoration(TypedListSpacingDecoration(context, false))
checkNotNull(sourceSelectionController).attachToRecyclerView(this)
} }
addMenuProvider(ExploreMenuProvider(binding.root.context)) addMenuProvider(ExploreMenuProvider(binding.root.context))
viewModel.content.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner) {
@@ -100,6 +111,7 @@ class ExploreFragment :
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
sourceSelectionController = null
exploreAdapter = null exploreAdapter = null
} }
@@ -147,18 +159,15 @@ class ExploreFragment :
} }
override fun onItemClick(item: MangaSourceItem, view: View) { override fun onItemClick(item: MangaSourceItem, view: View) {
if (sourceSelectionController?.onItemClick(item.id) == true) {
return
}
val intent = MangaListActivity.newIntent(view.context, item.source) val intent = MangaListActivity.newIntent(view.context, item.source)
startActivity(intent) startActivity(intent)
} }
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean { override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
val menu = PopupMenu(view.context, view) return sourceSelectionController?.onItemLongClick(item.id) ?: false
menu.inflate(R.menu.popup_source)
menu.menu.findItem(R.id.action_shortcut)
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(view.context)
menu.setOnMenuItemClickListener(SourceMenuListener(item))
menu.show()
return true
} }
override fun onRetryClick(error: Throwable) = Unit override fun onRetryClick(error: Throwable) = Unit
@@ -167,6 +176,54 @@ class ExploreFragment :
startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java)) startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java))
} }
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
viewBinding?.recyclerView?.invalidateItemDecorations()
}
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_source, menu)
return true
}
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
val isSingleSelection = controller.count == 1
menu.findItem(R.id.action_settings).isVisible = isSingleSelection
menu.findItem(R.id.action_shortcut).isVisible = isSingleSelection
return super.onPrepareActionMode(controller, mode, menu)
}
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
val selectedSources = controller.peekCheckedIds().mapNotNullToSet { id ->
MangaSource.entries.getOrNull(id.toInt())
}
if (selectedSources.isEmpty()) {
return false
}
when (item.itemId) {
R.id.action_settings -> {
val source = selectedSources.singleOrNull() ?: return false
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source))
mode.finish()
}
R.id.action_disable -> {
viewModel.disableSources(selectedSources)
mode.finish()
}
R.id.action_shortcut -> {
val source = selectedSources.singleOrNull() ?: return false
viewLifecycleScope.launch {
shortcutManager.requestPinShortcut(source)
}
mode.finish()
}
else -> return false
}
return true
}
private fun onOpenManga(manga: Manga) { private fun onOpenManga(manga: Manga) {
val intent = DetailsActivity.newIntent(context ?: return, manga) val intent = DetailsActivity.newIntent(context ?: return, manga)
startActivity(intent) startActivity(intent)
@@ -194,30 +251,4 @@ class ExploreFragment :
.create() .create()
.show() .show()
} }
private inner class SourceMenuListener(
private val sourceItem: MangaSourceItem,
) : PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_settings -> {
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), sourceItem.source))
}
R.id.action_disable -> {
viewModel.disableSource(sourceItem.source)
}
R.id.action_shortcut -> {
viewLifecycleScope.launch {
shortcutManager.requestPinShortcut(sourceItem.source)
}
}
else -> return false
}
return true
}
}
} }

View File

@@ -92,10 +92,11 @@ class ExploreViewModel @Inject constructor(
} }
} }
fun disableSource(source: MangaSource) { fun disableSources(sources: Collection<MangaSource>) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val rollback = sourcesRepository.setSourceEnabled(source, isEnabled = false) val rollback = sourcesRepository.setSourcesEnabled(sources, isEnabled = false)
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback)) val message = if (sources.size == 1) R.string.source_disabled else R.string.sources_disabled
onActionDone.call(ReversibleAction(message, rollback))
} }
} }

View File

@@ -0,0 +1,56 @@
package org.koitharu.kotatsu.explore.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_ID
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.core.util.ext.getItem
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
import com.google.android.material.R as materialR
class SourceSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
private val fillColor = ColorUtils.setAlphaComponent(
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
0x74,
)
private val defaultRadius = context.resources.getDimension(R.dimen.list_selector_corner)
init {
hasBackground = false
hasForeground = true
isIncludeDecorAndMargins = false
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
}
override fun getItemId(parent: RecyclerView, child: View): Long {
val holder = parent.getChildViewHolder(child) ?: return NO_ID
val item = holder.getItem(MangaSourceItem::class.java) ?: return NO_ID
return item.id
}
override fun onDrawForeground(
canvas: Canvas,
parent: RecyclerView,
child: View,
bounds: RectF,
state: RecyclerView.State,
) {
paint.color = fillColor
paint.style = Paint.Style.FILL
canvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)
paint.color = strokeColor
paint.style = Paint.Style.STROKE
canvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)
}
}

View File

@@ -8,6 +8,9 @@ data class MangaSourceItem(
val isGrid: Boolean, val isGrid: Boolean,
) : ListModel { ) : ListModel {
val id: Long
get() = source.ordinal.toLong()
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is MangaSourceItem && other.source == source return other is MangaSourceItem && other.source == source
} }

View File

@@ -81,7 +81,7 @@ class SearchSuggestionViewModel @Inject constructor(
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) { fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
sourcesRepository.setSourceEnabled(source, isEnabled) sourcesRepository.setSourcesEnabled(setOf(source), isEnabled)
} }
} }

View File

@@ -45,7 +45,7 @@ class NewSourcesViewModel @Inject constructor(
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) { fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
repository.setSourceEnabled(item.source, isEnabled) repository.setSourcesEnabled(setOf(item.source), isEnabled)
} }
} }
} }

View File

@@ -18,7 +18,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import javax.inject.Inject import javax.inject.Inject
@@ -75,7 +74,7 @@ class SourceSettingsViewModel @Inject constructor(
fun setEnabled(value: Boolean) { fun setEnabled(value: Boolean) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
mangaSourcesRepository.setSourceEnabled(source, value) mangaSourcesRepository.setSourcesEnabled(setOf(source), value)
} }
} }

View File

@@ -70,7 +70,7 @@ class SourcesCatalogViewModel @Inject constructor(
fun addSource(source: MangaSource) { fun addSource(source: MangaSource) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val rollback = repository.setSourceEnabled(source, true) val rollback = repository.setSourcesEnabled(setOf(source), true)
onActionDone.call(ReversibleAction(R.string.source_enabled, rollback)) onActionDone.call(ReversibleAction(R.string.source_enabled, rollback))
} }
} }

View File

@@ -64,7 +64,7 @@ class SourcesManageViewModel @Inject constructor(
fun setEnabled(source: MangaSource, isEnabled: Boolean) { fun setEnabled(source: MangaSource, isEnabled: Boolean) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
val rollback = repository.setSourceEnabled(source, isEnabled) val rollback = repository.setSourcesEnabled(setOf(source), isEnabled)
if (!isEnabled) { if (!isEnabled) {
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback)) onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
} }

View File

@@ -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="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z" />
</vector>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_disable"
android:icon="@drawable/ic_eye_off"
android:title="@string/disable"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_shortcut"
android:icon="@drawable/ic_pin"
android:title="@string/create_shortcut"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:title="@string/settings"
app:showAsAction="ifRoom|withText" />
</menu>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_settings"
android:title="@string/settings" />
<item
android:id="@+id/action_shortcut"
android:title="@string/create_shortcut" />
<item
android:id="@+id/action_disable"
android:title="@string/disable" />
</menu>

View File

@@ -642,4 +642,5 @@
<string name="authors">Authors</string> <string name="authors">Authors</string>
<string name="blocked_by_server_message">You are blocked by the server. Try to use a different network connection (VPN, Proxy, etc.)</string> <string name="blocked_by_server_message">You are blocked by the server. Try to use a different network connection (VPN, Proxy, etc.)</string>
<string name="disable">Disable</string> <string name="disable">Disable</string>
<string name="sources_disabled">Sources disabled</string>
</resources> </resources>