Convert extension details to full Compose

This commit is contained in:
arkon 2022-08-29 16:10:55 -04:00
parent 488d8ab8cf
commit 761635b572
5 changed files with 143 additions and 174 deletions

View File

@ -11,6 +11,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@ -20,9 +21,12 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -41,22 +45,25 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.browse.components.ExtensionIcon import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DIVIDER_ALPHA import eu.kanade.presentation.components.DIVIDER_ALPHA
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
@ -66,11 +73,68 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
fun ExtensionDetailsScreen( fun ExtensionDetailsScreen(
nestedScrollInterop: NestedScrollConnection, navigateUp: () -> Unit,
presenter: ExtensionDetailsPresenter,
onClickSourcePreferences: (sourceId: Long) -> Unit,
) {
val uriHandler = LocalUriHandler.current
Scaffold(
modifier = Modifier.statusBarsPadding(),
topBar = {
AppBar(
title = stringResource(R.string.label_extension_info),
navigateUp = navigateUp,
actions = {
AppBarActions(
actions = buildList {
if (presenter.extension?.isUnofficial == false) {
add(
AppBar.Action(
title = stringResource(R.string.whats_new),
icon = Icons.Outlined.History,
onClick = { uriHandler.openUri(presenter.getChangelogUrl()) },
),
)
add(
AppBar.Action(
title = stringResource(R.string.action_faq_and_guides),
icon = Icons.Outlined.HelpOutline,
onClick = { uriHandler.openUri(presenter.getReadmeUrl()) },
),
)
}
addAll(
listOf(
AppBar.OverflowAction(
title = stringResource(R.string.action_enable_all),
onClick = { presenter.toggleSources(true) },
),
AppBar.OverflowAction(
title = stringResource(R.string.action_disable_all),
onClick = { presenter.toggleSources(false) },
),
AppBar.OverflowAction(
title = stringResource(R.string.pref_clear_cookies),
onClick = { presenter.clearCookies() },
),
),
)
},
)
},
)
},
) { paddingValues ->
ExtensionDetails(paddingValues, presenter, onClickSourcePreferences)
}
}
@Composable
private fun ExtensionDetails(
paddingValues: PaddingValues,
presenter: ExtensionDetailsPresenter, presenter: ExtensionDetailsPresenter,
onClickUninstall: () -> Unit,
onClickSourcePreferences: (sourceId: Long) -> Unit, onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickSource: (sourceId: Long) -> Unit,
) { ) {
when { when {
presenter.isLoading -> LoadingScreen() presenter.isLoading -> LoadingScreen()
@ -81,8 +145,7 @@ fun ExtensionDetailsScreen(
var showNsfwWarning by remember { mutableStateOf(false) } var showNsfwWarning by remember { mutableStateOf(false) }
ScrollbarLazyColumn( ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop), contentPadding = paddingValues + WindowInsets.navigationBars.asPaddingValues(),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) { ) {
when { when {
extension.isUnofficial -> extension.isUnofficial ->
@ -98,7 +161,7 @@ fun ExtensionDetailsScreen(
item { item {
DetailsHeader( DetailsHeader(
extension = extension, extension = extension,
onClickUninstall = onClickUninstall, onClickUninstall = { presenter.uninstallExtension() },
onClickAppInfo = { onClickAppInfo = {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", extension.pkgName, null) data = Uri.fromParts("package", extension.pkgName, null)
@ -119,7 +182,7 @@ fun ExtensionDetailsScreen(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
source = source, source = source,
onClickSourcePreferences = onClickSourcePreferences, onClickSourcePreferences = onClickSourcePreferences,
onClickSource = onClickSource, onClickSource = { presenter.toggleSource(it) },
) )
} }
} }

View File

@ -2,135 +2,35 @@ package eu.kanade.tachiyomi.ui.browse.extension.details
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import eu.kanade.presentation.browse.ExtensionDetailsScreen import eu.kanade.presentation.browse.ExtensionDetailsScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.util.system.logcat
import okhttp3.HttpUrl.Companion.toHttpUrl
import uy.kohesive.injekt.injectLazy
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) : class ExtensionDetailsController(
ComposeController<ExtensionDetailsPresenter>(bundle) { bundle: Bundle? = null,
) : FullComposeController<ExtensionDetailsPresenter>(bundle) {
private val network: NetworkHelper by injectLazy()
constructor(pkgName: String) : this( constructor(pkgName: String) : this(
bundleOf(PKGNAME_KEY to pkgName), bundleOf(PKGNAME_KEY to pkgName),
) )
init {
setHasOptionsMenu(true)
}
override fun getTitle() = resources?.getString(R.string.label_extension_info)
override fun createPresenter() = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!) override fun createPresenter() = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
@Composable @Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { override fun ComposeContent() {
ExtensionDetailsScreen( ExtensionDetailsScreen(
nestedScrollInterop = nestedScrollInterop, navigateUp = router::popCurrentController,
presenter = presenter, presenter = presenter,
onClickUninstall = { presenter.uninstallExtension() },
onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) }, onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
onClickSource = { presenter.toggleSource(it) },
) )
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.extension_details, menu)
presenter.extension?.let { extension ->
menu.findItem(R.id.action_history).isVisible = !extension.isUnofficial
menu.findItem(R.id.action_faq_and_guides).isVisible = !extension.isUnofficial
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_history -> openChangelog()
R.id.action_faq_and_guides -> openReadme()
R.id.action_enable_all -> toggleAllSources(true)
R.id.action_disable_all -> toggleAllSources(false)
R.id.action_clear_cookies -> clearCookies()
}
return super.onOptionsItemSelected(item)
}
fun onExtensionUninstalled() { fun onExtensionUninstalled() {
router.popCurrentController() router.popCurrentController()
} }
private fun toggleAllSources(enable: Boolean) {
presenter.toggleSources(enable)
}
private fun openChangelog() {
val extension = presenter.extension!!
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val pkgFactory = extension.pkgFactory
if (extension.hasChangelog) {
val url = createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/CHANGELOG.md")
openInBrowser(url)
return
}
// Falling back on GitHub commit history because there is no explicit changelog in extension
val url = createUrl(URL_EXTENSION_COMMITS, pkgName, pkgFactory)
openInBrowser(url)
}
private fun openReadme() {
val extension = presenter.extension!!
if (!extension.hasReadme) {
openInBrowser("https://tachiyomi.org/help/faq/#extensions")
return
}
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val pkgFactory = extension.pkgFactory
val url = createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/README.md")
openInBrowser(url)
return
}
private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
return if (!pkgFactory.isNullOrEmpty()) {
when (path.isEmpty()) {
true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
}
} else {
url + "/src/" + pkgName.replace(".", "/") + path
}
}
private fun clearCookies() {
val urls = presenter.extension?.sources
?.filterIsInstance<HttpSource>()
?.map { it.baseUrl }
?.distinct() ?: emptyList()
val cleared = urls.sumOf {
network.cookieManager.remove(it.toHttpUrl())
}
logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
}
} }
private const val PKGNAME_KEY = "pkg_name" private const val PKGNAME_KEY = "pkg_name"
private const val URL_EXTENSION_COMMITS = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
private const val URL_EXTENSION_BLOB = "https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master"

View File

@ -7,14 +7,18 @@ import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.presentation.browse.ExtensionDetailsState import eu.kanade.presentation.browse.ExtensionDetailsState
import eu.kanade.presentation.browse.ExtensionDetailsStateImpl import eu.kanade.presentation.browse.ExtensionDetailsStateImpl
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import okhttp3.HttpUrl.Companion.toHttpUrl
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -24,6 +28,7 @@ class ExtensionDetailsPresenter(
private val context: Application = Injekt.get(), private val context: Application = Injekt.get(),
private val getExtensionSources: GetExtensionSources = Injekt.get(), private val getExtensionSources: GetExtensionSources = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(), private val toggleSource: ToggleSource = Injekt.get(),
private val network: NetworkHelper = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(),
) : BasePresenter<ExtensionDetailsController>(), ExtensionDetailsState by state { ) : BasePresenter<ExtensionDetailsController>(), ExtensionDetailsState by state {
@ -32,7 +37,7 @@ class ExtensionDetailsPresenter(
presenterScope.launchIO { presenterScope.launchIO {
extensionManager.getInstalledExtensionsFlow() extensionManager.getInstalledExtensionsFlow()
.map { it.firstOrNull { it.pkgName == pkgName } } .map { it.firstOrNull { pkg-> pkg.pkgName == pkgName } }
.collectLatest { extension -> .collectLatest { extension ->
// If extension is null it's most likely uninstalled // If extension is null it's most likely uninstalled
if (extension == null) { if (extension == null) {
@ -65,6 +70,44 @@ class ExtensionDetailsPresenter(
} }
} }
fun getChangelogUrl(): String {
extension ?: return ""
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val pkgFactory = extension.pkgFactory
if (extension.hasChangelog) {
return createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/CHANGELOG.md")
}
// Falling back on GitHub commit history because there is no explicit changelog in extension
return createUrl(URL_EXTENSION_COMMITS, pkgName, pkgFactory)
}
fun getReadmeUrl(): String {
extension ?: return ""
if (!extension.hasReadme) {
return "https://tachiyomi.org/help/faq/#extensions"
}
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val pkgFactory = extension.pkgFactory
return createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/README.md")
}
fun clearCookies() {
val urls = extension?.sources
?.filterIsInstance<HttpSource>()
?.map { it.baseUrl }
?.distinct() ?: emptyList()
val cleared = urls.sumOf {
network.cookieManager.remove(it.toHttpUrl())
}
logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
}
fun uninstallExtension() { fun uninstallExtension() {
val extension = extension ?: return val extension = extension ?: return
extensionManager.uninstallExtension(extension.pkgName) extensionManager.uninstallExtension(extension.pkgName)
@ -77,6 +120,17 @@ class ExtensionDetailsPresenter(
fun toggleSources(enable: Boolean) { fun toggleSources(enable: Boolean) {
extension?.sources?.forEach { toggleSource.await(it.id, enable) } extension?.sources?.forEach { toggleSource.await(it.id, enable) }
} }
private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
return if (!pkgFactory.isNullOrEmpty()) {
when (path.isEmpty()) {
true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
}
} else {
url + "/src/" + pkgName.replace(".", "/") + path
}
}
} }
data class ExtensionSourceItem( data class ExtensionSourceItem(
@ -84,3 +138,6 @@ data class ExtensionSourceItem(
val enabled: Boolean, val enabled: Boolean,
val labelAsName: Boolean, val labelAsName: Boolean,
) )
private const val URL_EXTENSION_COMMITS = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
private const val URL_EXTENSION_BLOB = "https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master"

View File

@ -99,25 +99,14 @@ class MangaPresenter(
) : BasePresenter<MangaController>() { ) : BasePresenter<MangaController>() {
private val _state: MutableStateFlow<MangaScreenState> = MutableStateFlow(MangaScreenState.Loading) private val _state: MutableStateFlow<MangaScreenState> = MutableStateFlow(MangaScreenState.Loading)
val state = _state.asStateFlow() val state = _state.asStateFlow()
private val successState: MangaScreenState.Success? private val successState: MangaScreenState.Success?
get() = state.value as? MangaScreenState.Success get() = state.value as? MangaScreenState.Success
/**
* Subscription to update the manga from the source.
*/
private var fetchMangaJob: Job? = null private var fetchMangaJob: Job? = null
/**
* Subscription to retrieve the new list of chapters from the source.
*/
private var fetchChaptersJob: Job? = null private var fetchChaptersJob: Job? = null
/**
* Subscription to observe download status changes.
*/
private var observeDownloadsStatusJob: Job? = null private var observeDownloadsStatusJob: Job? = null
private var observeDownloadsPageJob: Job? = null private var observeDownloadsPageJob: Job? = null
@ -138,7 +127,7 @@ class MangaPresenter(
val isFavoritedManga: Boolean val isFavoritedManga: Boolean
get() = manga?.favorite ?: false get() = manga?.favorite ?: false
val processedChapters: Sequence<ChapterItem>? private val processedChapters: Sequence<ChapterItem>?
get() = successState?.processedChapters get() = successState?.processedChapters
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
@ -164,8 +153,6 @@ class MangaPresenter(
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
// Manga info - start
presenterScope.launchIO { presenterScope.launchIO {
val manga = getMangaAndChapters.awaitManga(mangaId) val manga = getMangaAndChapters.awaitManga(mangaId)
@ -221,15 +208,11 @@ class MangaPresenter(
} }
preferences.incognitoMode() preferences.incognitoMode()
.asHotFlow { incognito -> .asHotFlow { incognitoMode = it }
incognitoMode = incognito
}
.launchIn(presenterScope) .launchIn(presenterScope)
preferences.downloadedOnly() preferences.downloadedOnly()
.asHotFlow { downloadedOnly -> .asHotFlow { downloadedOnlyMode = it }
downloadedOnlyMode = downloadedOnly
}
.launchIn(presenterScope) .launchIn(presenterScope)
} }
@ -239,6 +222,7 @@ class MangaPresenter(
} }
// Manga info - start // Manga info - start
/** /**
* Fetch manga information from source. * Fetch manga information from source.
*/ */
@ -395,7 +379,7 @@ class MangaPresenter(
* @param manga the manga to get categories from. * @param manga the manga to get categories from.
* @return Array of category ids the manga is in, if none returns default id * @return Array of category ids the manga is in, if none returns default id
*/ */
suspend fun getMangaCategoryIds(manga: DomainManga): List<Long> { private suspend fun getMangaCategoryIds(manga: DomainManga): List<Long> {
return getCategories.await(manga.id) return getCategories.await(manga.id)
.map { it.id } .map { it.id }
} }
@ -420,7 +404,7 @@ class MangaPresenter(
moveMangaToCategory(categoryIds) moveMangaToCategory(categoryIds)
} }
fun moveMangaToCategory(categoryIds: List<Long>) { private fun moveMangaToCategory(categoryIds: List<Long>) {
presenterScope.launchIO { presenterScope.launchIO {
setMangaCategories.await(mangaId, categoryIds) setMangaCategories.await(mangaId, categoryIds)
} }
@ -951,7 +935,7 @@ class MangaPresenter(
.lastOrNull() .lastOrNull()
?.chapterNumber?.toDouble() ?: -1.0 ?.chapterNumber?.toDouble() ?: -1.0
if (latestLocalReadChapterNumber >= track.lastChapterRead) { if (latestLocalReadChapterNumber > track.lastChapterRead) {
val updatedTrack = track.copy( val updatedTrack = track.copy(
lastChapterRead = latestLocalReadChapterNumber, lastChapterRead = latestLocalReadChapterNumber,
) )

View File

@ -1,35 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_history"
android:icon="@drawable/ic_history_24dp"
android:title="@string/whats_new"
android:visible="false"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_faq_and_guides"
android:icon="@drawable/ic_help_24dp"
android:title="@string/action_faq_and_guides"
android:visible="false"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_enable_all"
android:title="@string/action_enable_all"
app:showAsAction="never" />
<item
android:id="@+id/action_disable_all"
android:title="@string/action_disable_all"
app:showAsAction="never" />
<item
android:id="@+id/action_clear_cookies"
android:title="@string/pref_clear_cookies"
app:showAsAction="never" />
</menu>