updated ui and logic for plugins

This commit is contained in:
reduplicated 2022-08-07 23:11:13 +02:00
parent dd25523bea
commit 4e6bbf3908
11 changed files with 288 additions and 151 deletions

View File

@ -173,6 +173,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_settings_account,
R.id.navigation_settings_lang,
R.id.navigation_settings_general,
R.id.navigation_settings_extensions,
R.id.navigation_settings_plugins,
).contains(destination.id)
val landscape = when (resources.configuration.orientation) {
@ -422,9 +424,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this)
PluginManager.updateAllOnlinePlugins(applicationContext)
PluginManager.loadAllLocalPlugins(applicationContext)
PluginManager.loadAllOnlinePlugins(applicationContext)
PluginManager.updateAllOnlinePlugins(this)
PluginManager.loadAllLocalPlugins(this)
PluginManager.loadAllOnlinePlugins(this)
// ioSafe {
// val plugins =

View File

@ -8,6 +8,7 @@ import android.content.res.Resources
import android.os.Environment
import android.widget.Toast
import android.app.Activity
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
@ -52,33 +53,31 @@ object PluginManager {
// Prevent multiple writes at once
val lock = Mutex()
const val TAG = "PluginManager"
/**
* Store data about the plugin for fetching later
* */
private fun setPluginData(data: PluginData) {
ioSafe {
lock.withLock {
if (data.isOnline) {
val plugins = getPluginsOnline()
setKey(PLUGINS_KEY, plugins + data)
} else {
val plugins = getPluginsLocal()
setKey(PLUGINS_KEY_LOCAL, plugins + data)
}
private suspend fun setPluginData(data: PluginData) {
lock.withLock {
if (data.isOnline) {
val plugins = getPluginsOnline()
setKey(PLUGINS_KEY, plugins + data)
} else {
val plugins = getPluginsLocal()
setKey(PLUGINS_KEY_LOCAL, plugins + data)
}
}
}
private fun deletePluginData(data: PluginData) {
ioSafe {
lock.withLock {
if (data.isOnline) {
val plugins = getPluginsOnline().filter { it.url != data.url }
setKey(PLUGINS_KEY, plugins)
} else {
val plugins = getPluginsLocal().filter { it.filePath != data.filePath }
setKey(PLUGINS_KEY_LOCAL, plugins + data)
}
private suspend fun deletePluginData(data: PluginData) {
lock.withLock {
if (data.isOnline) {
val plugins = getPluginsOnline().filter { it.url != data.url }
setKey(PLUGINS_KEY, plugins)
} else {
val plugins = getPluginsLocal().filter { it.filePath != data.filePath }
setKey(PLUGINS_KEY_LOCAL, plugins + data)
}
}
}
@ -105,11 +104,11 @@ object PluginManager {
private var loadedLocalPlugins = false
private val gson = Gson()
private fun maybeLoadPlugin(context: Context, file: File) {
private suspend fun maybeLoadPlugin(activity: Activity, file: File) {
val name = file.name
if (file.extension == "zip" || file.extension == "cs3") {
loadPlugin(
context,
activity,
file,
PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
@ -119,7 +118,7 @@ object PluginManager {
/**
* Needs to be run before other plugin loading because plugin loading can not be overwritten
**/
fun updateAllOnlinePlugins(context: Context) {
fun updateAllOnlinePlugins(activity: Activity) {
val urls = getKey<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()
val onlinePlugins = urls.toList().apmap {
@ -136,28 +135,28 @@ object PluginManager {
}
}.flatten()
println("Outdated plugins: $outdatedPlugins")
Log.i(TAG, "Outdated plugins: $outdatedPlugins")
outdatedPlugins.apmap {
downloadAndLoadPlugin(
context,
activity,
it.second.second.url,
it.first.internalName,
it.second.first
)
}
println("Plugin update done!")
Log.i(TAG, "Plugin update done!")
}
fun loadAllOnlinePlugins(context: Context) {
File(context.filesDir, ONLINE_PLUGINS_FOLDER).listFiles()?.sortedBy { it.name }
?.forEach { file ->
maybeLoadPlugin(context, file)
fun loadAllOnlinePlugins(activity: Activity) {
File(activity.filesDir, ONLINE_PLUGINS_FOLDER).listFiles()?.sortedBy { it.name }
?.apmap { file ->
maybeLoadPlugin(activity, file)
}
}
fun loadAllLocalPlugins(context: Context) {
fun loadAllLocalPlugins(activity: Activity) {
val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL)
@ -172,8 +171,8 @@ object PluginManager {
val sortedPlugins = dir.listFiles()
// Always sort plugins alphabetically for reproducible results
sortedPlugins?.sortedBy { it.name }?.forEach { file ->
maybeLoadPlugin(context, file)
sortedPlugins?.sortedBy { it.name }?.apmap { file ->
maybeLoadPlugin(activity, file)
}
loadedLocalPlugins = true
@ -182,14 +181,14 @@ object PluginManager {
/**
* @return True if successful, false if not
* */
private fun loadPlugin(context: Context, file: File, data: PluginData): Boolean {
private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean {
val fileName = file.nameWithoutExtension
val filePath = file.absolutePath
println("Loading plugin: $data")
Log.i(TAG, "Loading plugin: $data")
//logger.info("Loading plugin: " + fileName);
return try {
val loader = PathClassLoader(filePath, context.classLoader)
val loader = PathClassLoader(filePath, activity.classLoader)
var manifest: Plugin.Manifest
loader.getResourceAsStream("manifest.json").use { stream ->
if (stream == null) {
@ -211,14 +210,15 @@ object PluginManager {
loader.loadClass(manifest.pluginClassName) as Class<out Plugin?>
val pluginInstance: Plugin =
pluginClass.newInstance() as Plugin
if (plugins.containsKey(filePath)) {
println("Plugin with name $name already exists")
return true
}
// Sets with the proper version
setPluginData(data.copy(version = version))
if (plugins.containsKey(filePath)) {
Log.i(TAG, "Plugin with name $name already exists")
return true
}
pluginInstance.__filename = fileName
if (pluginInstance.needsResources) {
// based on https://stackoverflow.com/questions/7483568/dynamic-resource-loading-from-other-apk
@ -228,21 +228,21 @@ object PluginManager {
addAssetPath.invoke(assets, file.absolutePath)
pluginInstance.resources = Resources(
assets,
context.resources.displayMetrics,
context.resources.configuration
activity.resources.displayMetrics,
activity.resources.configuration
)
}
plugins[filePath] = pluginInstance
classLoaders[loader] = pluginInstance
pluginInstance.load(context)
println("Loaded plugin ${data.internalName} successfully")
pluginInstance.load(activity)
Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
true
} catch (e: Throwable) {
failedToLoad[file] = e
e.printStackTrace()
showToast(
context as Activity,
context.getString(R.string.plugin_load_fail).format(fileName),
activity,
activity.getString(R.string.plugin_load_fail).format(fileName),
Toast.LENGTH_LONG
)
false
@ -250,7 +250,7 @@ object PluginManager {
}
suspend fun downloadAndLoadPlugin(
context: Context,
activity: Activity,
pluginUrl: String,
internalName: String,
repositoryUrl: String
@ -260,21 +260,28 @@ object PluginManager {
true
) + "." + repositoryUrl.hashCode()) // Guaranteed unique
val fileName = (sanitizeFilename(internalName, true) + "." + internalName.hashCode())
println("Downloading plugin: $pluginUrl to $folderName/$fileName")
Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
val file = downloadPluginToFile(context, pluginUrl, fileName, folderName)
val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
return loadPlugin(
context,
activity,
file ?: return false,
PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
}
fun deletePlugin(context: Context, pluginUrl: String, name: String): Boolean {
suspend fun deletePlugin(pluginUrl: String): Boolean {
val data = getPluginsOnline()
.firstOrNull { it.url == pluginUrl }
?: return false
deletePluginData(data)
return File(data.filePath).delete()
return try {
if (File(data.filePath).delete()) {
deletePluginData(data)
return true
}
false
} catch (e: Exception) {
false
}
}
}

View File

@ -11,7 +11,6 @@ import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
@ -24,6 +23,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.main_settings.*
import kotlinx.android.synthetic.main.settings_title_top.*
import kotlinx.android.synthetic.main.standard_toolbar.*
import java.io.File
class SettingsFragment : Fragment() {
@ -41,7 +41,19 @@ class SettingsFragment : Fragment() {
}
}
fun PreferenceFragmentCompat?.setUpToolbar(@StringRes title: Int) {
fun Fragment?.setUpToolbar(title: String) {
if (this == null) return
settings_toolbar?.apply {
setTitle(title)
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
setNavigationOnClickListener {
activity?.onBackPressed()
}
}
context.fixPaddingStatusbar(settings_toolbar)
}
fun Fragment?.setUpToolbar(@StringRes title: Int) {
if (this == null) return
settings_toolbar?.apply {
setTitle(title)
@ -138,7 +150,10 @@ class SettingsFragment : Fragment() {
Pair(settings_ui, R.id.action_navigation_settings_to_navigation_settings_ui),
Pair(settings_lang, R.id.action_navigation_settings_to_navigation_settings_lang),
Pair(settings_updates, R.id.action_navigation_settings_to_navigation_settings_updates),
Pair(settings_extensions, R.id.action_navigation_settings_to_navigation_settings_extensions),
Pair(
settings_extensions,
R.id.action_navigation_settings_to_navigation_settings_extensions
),
).forEach { (view, navigationId) ->
view?.apply {
setOnClickListener {

View File

@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -37,7 +38,9 @@ class ExtensionsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(extensions_root)
//context?.fixPaddingStatusbar(extensions_root)
setUpToolbar(R.string.extensions)
repo_recycler_view?.adapter = RepoAdapter(emptyArray(), {
findNavController().navigate(
@ -94,7 +97,5 @@ class ExtensionsFragment : Fragment() {
extensionViewModel.loadRepositories()
}
}

View File

@ -5,8 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.plugins.SitePlugin
data class RepositoryData(
@JsonProperty("name") val name: String,
@ -24,8 +22,4 @@ class ExtensionsViewModel : ViewModel() {
val urls = getKey<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()
_repositories.postValue(urls)
}
suspend fun getPlugins(repositoryUrl: String): List<Pair<String, SitePlugin>> {
return RepositoryManager.getRepoPlugins(repositoryUrl) ?: emptyList()
}
}

View File

@ -4,18 +4,29 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PluginData
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.SitePlugin
import com.lagradost.cloudstream3.ui.result.ActorAdaptor
import com.lagradost.cloudstream3.ui.result.DiffCallback
import com.lagradost.cloudstream3.ui.result.UiText
import kotlinx.android.synthetic.main.repository_item.view.*
data class PluginViewData(
val plugin: Plugin,
val isDownloaded: Boolean,
)
class PluginAdapter(
var plugins: List<Pair<String, SitePlugin>>,
val iconClickCallback: PluginAdapter.(repositoryUrl: String, plugin: SitePlugin, isDownloaded: Boolean) -> Unit
val iconClickCallback: (Plugin) -> Unit
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val plugins: MutableList<PluginViewData> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PluginViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.repository_item, parent, false)
@ -23,10 +34,9 @@ class PluginAdapter(
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val (repositoryUrl, plugin) = plugins[position]
when (holder) {
is PluginViewHolder -> {
holder.bind(repositoryUrl, plugin)
holder.bind(plugins[position])
}
}
}
@ -35,34 +45,61 @@ class PluginAdapter(
return plugins.size
}
fun updateList(newList: List<PluginViewData>) {
val diffResult = DiffUtil.calculateDiff(
PluginDiffCallback(this.plugins, newList)
)
plugins.clear()
plugins.addAll(newList)
diffResult.dispatchUpdatesTo(this)
}
/*
private var storedPlugins: Array<PluginData> = reloadStoredPlugins()
fun reloadStoredPlugins(): Array<PluginData> {
private fun reloadStoredPlugins(): Array<PluginData> {
return PluginManager.getPluginsOnline().also { storedPlugins = it }
}
}*/
inner class PluginViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bind(
repositoryUrl: String,
plugin: SitePlugin,
data: PluginViewData,
) {
val isDownloaded = storedPlugins.any { it.url == plugin.url }
val metadata = data.plugin.second
val drawableInt = if (isDownloaded)
val drawableInt = if (data.isDownloaded)
R.drawable.ic_baseline_delete_outline_24
else R.drawable.netflix_download
itemView.nsfw_marker?.isVisible = plugin.isAdult == true
itemView.nsfw_marker?.isVisible = metadata.isAdult == true
itemView.action_button?.setImageResource(drawableInt)
itemView.action_button?.setOnClickListener {
iconClickCallback.invoke(this@PluginAdapter, repositoryUrl, plugin, isDownloaded)
iconClickCallback.invoke(data.plugin)
}
itemView.main_text?.text = plugin.name
itemView.sub_text?.text = plugin.description
itemView.main_text?.text = metadata.name
itemView.sub_text?.text = metadata.description
}
}
}
class PluginDiffCallback(
private val oldList: List<PluginViewData>,
private val newList: List<PluginViewData>
) :
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].plugin.second.internalName == newList[newItemPosition].plugin.second.internalName && oldList[oldItemPosition].plugin.first == newList[newItemPosition].plugin.first
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}

View File

@ -9,7 +9,9 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -27,53 +29,31 @@ class PluginsFragment : Fragment() {
return inflater.inflate(R.layout.fragment_extensions, container, false)
}
private val extensionViewModel: ExtensionsViewModel by activityViewModels()
private val pluginViewModel: PluginsViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(extensions_root)
val name = arguments?.getString(PLUGINS_BUNDLE_NAME)
val url = arguments?.getString(PLUGINS_BUNDLE_URL)
if (url == null) {
activity?.onBackPressed()
return
}
ioSafe {
val plugins = extensionViewModel.getPlugins(url)
main {
repo_recycler_view?.adapter =
PluginAdapter(plugins) { repositoryUrl, plugin, isDownloaded ->
ioSafe {
val (success, message) = if (isDownloaded) {
PluginManager.deletePlugin(
view.context,
plugin.url,
plugin.name
) to R.string.plugin_deleted
} else {
PluginManager.downloadAndLoadPlugin(
view.context,
plugin.url,
plugin.name,
repositoryUrl
) to R.string.plugin_loaded
}
setUpToolbar(name ?: "Unknown")
println("Success: $success")
if (success) {
main {
showToast(activity, message, Toast.LENGTH_SHORT)
this@PluginAdapter.reloadStoredPlugins()
// Dirty and needs a fix
repo_recycler_view?.adapter?.notifyDataSetChanged()
}
}
}
}
repo_recycler_view?.adapter =
PluginAdapter {
pluginViewModel.handlePluginAction(activity, url, it)
}
observe(pluginViewModel.plugins) {
(repo_recycler_view?.adapter as? PluginAdapter?)?.updateList(it)
}
pluginViewModel.updatePluginList(url)
}
companion object {

View File

@ -0,0 +1,102 @@
package com.lagradost.cloudstream3.ui.settings.extensions
import android.app.Activity
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PluginData
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.plugins.SitePlugin
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import kotlinx.coroutines.launch
typealias Plugin = Pair<String, SitePlugin>
class PluginsViewModel : ViewModel() {
private val _plugins = MutableLiveData<List<PluginViewData>>()
val plugins: LiveData<List<PluginViewData>> = _plugins
private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf()
companion object {
const val TAG = "PLG"
}
private suspend fun getPlugins(
repositoryUrl: String,
canUseCache: Boolean = true
): List<Plugin> {
Log.i(TAG, "getPlugins = $repositoryUrl")
if (canUseCache && repositoryCache.containsKey(repositoryUrl)) {
repositoryCache[repositoryUrl]?.let {
return it
}
}
return RepositoryManager.getRepoPlugins(repositoryUrl)
?.also { repositoryCache[repositoryUrl] = it } ?: emptyList()
}
private fun getStoredPlugins(): Array<PluginData> {
return PluginManager.getPluginsOnline()
}
private fun getDownloads(): Set<String> {
return getStoredPlugins().map { it.internalName }.toSet()
}
private fun isDownloaded(plugin: Plugin, data: Set<String>? = null): Boolean {
return (data ?: getDownloads()).contains(plugin.second.internalName)
}
fun handlePluginAction(activity: Activity?, repositoryUrl: String, plugin: Plugin) = ioSafe {
Log.i(TAG, "handlePluginAction = $repositoryUrl, $plugin")
if (activity == null) return@ioSafe
val (repo, metadata) = plugin
val (success, message) = if (isDownloaded(plugin)) {
PluginManager.deletePlugin(
metadata.url,
) to R.string.plugin_deleted
} else {
PluginManager.downloadAndLoadPlugin(
activity,
metadata.url,
metadata.name,
repo
) to R.string.plugin_loaded
}
runOnMainThread {
if (success)
showToast(activity, message, Toast.LENGTH_SHORT)
else
showToast(activity, R.string.error, Toast.LENGTH_SHORT)
}
if (success)
updatePluginListPrivate(repositoryUrl)
}
private suspend fun updatePluginListPrivate(repositoryUrl: String) {
val stored = getDownloads()
val plugins = getPlugins(repositoryUrl)
val list = plugins.map { plugin ->
PluginViewData(plugin, isDownloaded(plugin, stored))
}
_plugins.postValue(list)
}
fun updatePluginList(repositoryUrl: String) = viewModelScope.launch {
Log.i(TAG, "updatePluginList = $repositoryUrl")
updatePluginListPrivate(repositoryUrl)
}
}

View File

@ -1,34 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/extensions_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/extensions_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
<include layout="@layout/standard_toolbar" />
<androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/repository_item"
android:id="@+id/repo_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/add_repo_button"
style="@style/ExtendedFloatingActionButton"
android:text="@string/add_repository"
android:textColor="?attr/textColor"
app:icon="@drawable/ic_baseline_add_24"
tools:ignore="ContentDescription" />
android:id="@+id/add_repo_button"
style="@style/ExtendedFloatingActionButton"
android:text="@string/add_repository"
android:textColor="?attr/textColor"
app:icon="@drawable/ic_baseline_add_24"
tools:ignore="ContentDescription" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -9,22 +9,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settings_toolbar"
android:paddingTop="@dimen/navbar_height"
tools:title="Overlord"
android:background="?attr/primaryGrayBackground"
app:navigationIconTint="?attr/iconColor"
app:titleTextColor="?attr/textColor"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/standard_toolbar" />
<!-- Required ViewGroup for PreferenceFragmentCompat -->
<FrameLayout

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settings_toolbar"
android:paddingTop="@dimen/navbar_height"
tools:title="Overlord"
android:background="?attr/primaryGrayBackground"
app:navigationIconTint="?attr/iconColor"
app:titleTextColor="?attr/textColor"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>