Started implementing server colors

This commit is contained in:
Nite 2021-10-14 17:17:32 +02:00
parent f752307a38
commit 23cca33d5a
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
14 changed files with 189 additions and 31 deletions

View File

@ -1,5 +1,5 @@
ext.versions = [ ext.versions = [
minSdk : 14, minSdk : 21,
targetSdk : 29, targetSdk : 29,
compileSdk : 29, compileSdk : 29,
// You need to run ./gradlew wrapper after updating the version // You need to run ./gradlew wrapper after updating the version
@ -42,6 +42,7 @@ ext.versions = [
dexter : "6.2.3", dexter : "6.2.3",
timber : "4.7.1", timber : "4.7.1",
fastScroll : "2.0.1", fastScroll : "2.0.1",
colorPicker : "2.2.3",
] ]
ext.gradlePlugins = [ ext.gradlePlugins = [
@ -89,6 +90,7 @@ ext.other = [
timber : "com.jakewharton.timber:timber:$versions.timber", timber : "com.jakewharton.timber:timber:$versions.timber",
fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll", fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll",
sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView", sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView",
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
] ]
ext.testing = [ ext.testing = [

View File

@ -105,6 +105,7 @@ dependencies {
implementation other.okhttpLogging implementation other.okhttpLogging
implementation other.fastScroll implementation other.fastScroll
implementation other.sortListView implementation other.sortListView
implementation other.colorPickerView
kapt androidSupport.room kapt androidSupport.room

View File

@ -9,7 +9,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
* Room Database to be used to store global data for the whole app. * Room Database to be used to store global data for the whole app.
* This could be settings or data that are not specific to any remote music database * This could be settings or data that are not specific to any remote music database
*/ */
@Database(entities = [ServerSetting::class], version = 3) @Database(entities = [ServerSetting::class], version = 4)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
/** /**
@ -42,3 +42,11 @@ val MIGRATION_2_3: Migration = object : Migration(2, 3) {
) )
} }
} }
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ALTER TABLE ServerSetting ADD COLUMN color INTEGER"
)
}
}

View File

@ -23,6 +23,7 @@ data class ServerSetting(
@ColumnInfo(name = "index") var index: Int, @ColumnInfo(name = "index") var index: Int,
@ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "name") var name: String,
@ColumnInfo(name = "url") var url: String, @ColumnInfo(name = "url") var url: String,
@ColumnInfo(name = "color") var color: Int? = null,
@ColumnInfo(name = "userName") var userName: String, @ColumnInfo(name = "userName") var userName: String,
@ColumnInfo(name = "password") var password: String, @ColumnInfo(name = "password") var password: String,
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean, @ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
@ -36,9 +37,9 @@ data class ServerSetting(
@ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null @ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null
) { ) {
constructor() : this ( constructor() : this (
-1, 0, "", "", "", "", false, false, false, null, null -1, 0, "", "", null, "", "", false, false, false, null, null
) )
constructor(name: String, url: String) : this( constructor(name: String, url: String) : this(
-1, 0, name, url, "", "", false, false, false, null, null -1, 0, name, url, null, "", "", false, false, false, null, null
) )
} }

View File

@ -8,6 +8,7 @@ import org.koin.dsl.module
import org.moire.ultrasonic.data.AppDatabase import org.moire.ultrasonic.data.AppDatabase
import org.moire.ultrasonic.data.MIGRATION_1_2 import org.moire.ultrasonic.data.MIGRATION_1_2
import org.moire.ultrasonic.data.MIGRATION_2_3 import org.moire.ultrasonic.data.MIGRATION_2_3
import org.moire.ultrasonic.data.MIGRATION_3_4
import org.moire.ultrasonic.fragment.ServerSettingsModel import org.moire.ultrasonic.fragment.ServerSettingsModel
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
@ -28,6 +29,7 @@ val appPermanentStorage = module {
) )
.addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4)
.build() .build()
} }

View File

@ -6,15 +6,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import java.io.IOException import com.skydoves.colorpickerview.ColorPickerDialog
import java.net.MalformedURLException import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
import java.net.URL
import java.util.Locale
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.BuildConfig
@ -35,6 +35,15 @@ import org.moire.ultrasonic.util.ModalBackgroundTask
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import retrofit2.Response import retrofit2.Response
import timber.log.Timber import timber.log.Timber
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import java.util.*
import com.skydoves.colorpickerview.flag.FlagMode
import com.skydoves.colorpickerview.flag.BubbleFlag
import org.moire.ultrasonic.util.ServerColor
/** /**
* Displays a form where server settings can be created / edited * Displays a form where server settings can be created / edited
@ -51,6 +60,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
private var serverNameEditText: TextInputLayout? = null private var serverNameEditText: TextInputLayout? = null
private var serverAddressEditText: TextInputLayout? = null private var serverAddressEditText: TextInputLayout? = null
private var serverColorImageView: ImageView? = null
private var userNameEditText: TextInputLayout? = null private var userNameEditText: TextInputLayout? = null
private var passwordEditText: TextInputLayout? = null private var passwordEditText: TextInputLayout? = null
private var selfSignedSwitch: SwitchMaterial? = null private var selfSignedSwitch: SwitchMaterial? = null
@ -59,6 +69,8 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
private var saveButton: Button? = null private var saveButton: Button? = null
private var testButton: Button? = null private var testButton: Button? = null
private var isInstanceStateSaved: Boolean = false private var isInstanceStateSaved: Boolean = false
private var currentColor: Int = 0
private var selectedColor: Int? = null
@Override @Override
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -79,6 +91,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
serverNameEditText = view.findViewById(R.id.edit_server_name) serverNameEditText = view.findViewById(R.id.edit_server_name)
serverAddressEditText = view.findViewById(R.id.edit_server_address) serverAddressEditText = view.findViewById(R.id.edit_server_address)
serverColorImageView = view.findViewById(R.id.edit_server_color_picker)
userNameEditText = view.findViewById(R.id.edit_server_username) userNameEditText = view.findViewById(R.id.edit_server_username)
passwordEditText = view.findViewById(R.id.edit_server_password) passwordEditText = view.findViewById(R.id.edit_server_password)
selfSignedSwitch = view.findViewById(R.id.edit_self_signed) selfSignedSwitch = view.findViewById(R.id.edit_self_signed)
@ -148,6 +161,32 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
testConnection() testConnection()
} }
} }
serverColorImageView!!.setOnClickListener {
val bubbleFlag = BubbleFlag(context)
bubbleFlag.flagMode = FlagMode.LAST
ColorPickerDialog.Builder(context).apply {
this.colorPickerView.setInitialColor(currentColor)
this.colorPickerView.setFlagView(bubbleFlag)
}
.attachAlphaSlideBar(false)
.setPositiveButton(getString(R.string.common_ok),
ColorEnvelopeListener { envelope, _ ->
selectedColor = envelope.color
updateColor(envelope.color) })
.setNegativeButton(getString(R.string.common_cancel)) {
dialogInterface, i -> dialogInterface.dismiss()
}
.setBottomSpace(12)
.show()
}
}
private fun updateColor(color: Int?) {
val image = ContextCompat.getDrawable(requireContext(), R.drawable.thumb_drawable)
currentColor = ServerColor.getBackgroundColor(requireContext(), color)
image?.setTint(currentColor)
serverColorImageView?.background = image
} }
override fun onBackPressed() { override fun onBackPressed() {
@ -176,6 +215,13 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
savedInstanceState.putBoolean( savedInstanceState.putBoolean(
::jukeboxSwitch.name, jukeboxSwitch!!.isChecked ::jukeboxSwitch.name, jukeboxSwitch!!.isChecked
) )
savedInstanceState.putInt(
::serverColorImageView.name, currentColor
)
if (selectedColor != null)
savedInstanceState.putInt(
::selectedColor.name, selectedColor!!
)
savedInstanceState.putBoolean( savedInstanceState.putBoolean(
::isInstanceStateSaved.name, true ::isInstanceStateSaved.name, true
) )
@ -203,6 +249,9 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
selfSignedSwitch!!.isChecked = savedInstanceState.getBoolean(::selfSignedSwitch.name) selfSignedSwitch!!.isChecked = savedInstanceState.getBoolean(::selfSignedSwitch.name)
ldapSwitch!!.isChecked = savedInstanceState.getBoolean(::ldapSwitch.name) ldapSwitch!!.isChecked = savedInstanceState.getBoolean(::ldapSwitch.name)
jukeboxSwitch!!.isChecked = savedInstanceState.getBoolean(::jukeboxSwitch.name) jukeboxSwitch!!.isChecked = savedInstanceState.getBoolean(::jukeboxSwitch.name)
updateColor(savedInstanceState.getInt(::serverColorImageView.name))
if (savedInstanceState.containsKey(::selectedColor.name))
selectedColor = savedInstanceState.getInt(::selectedColor.name)
isInstanceStateSaved = savedInstanceState.getBoolean(::isInstanceStateSaved.name) isInstanceStateSaved = savedInstanceState.getBoolean(::isInstanceStateSaved.name)
} }
@ -219,6 +268,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate
ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport
jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault
updateColor(currentServerSetting!!.color)
} }
/** /**
@ -267,6 +317,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
if (isValid) { if (isValid) {
currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString() currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString()
currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString() currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString()
currentServerSetting!!.color = selectedColor
currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString() currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString()
currentServerSetting!!.password = passwordEditText!!.editText?.text.toString() currentServerSetting!!.password = passwordEditText!!.editText?.text.toString()
currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked

View File

@ -14,10 +14,12 @@ import android.widget.ImageView
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.data.ServerSetting
import org.moire.ultrasonic.util.ServerColor
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
/** /**
@ -77,15 +79,15 @@ internal class ServerRowAdapter(
val text = vi?.findViewById<TextView>(R.id.server_name) val text = vi?.findViewById<TextView>(R.id.server_name)
val description = vi?.findViewById<TextView>(R.id.server_description) val description = vi?.findViewById<TextView>(R.id.server_description)
val layout = vi?.findViewById<RelativeLayout>(R.id.server_layout) val layout = vi?.findViewById<ConstraintLayout>(R.id.server_layout)
val image = vi?.findViewById<ImageView>(R.id.server_image) val image = vi?.findViewById<ImageView>(R.id.server_image)
val serverMenu = vi?.findViewById<ImageButton>(R.id.server_menu) val serverMenu = vi?.findViewById<ImageButton>(R.id.server_menu)
val setting = data.singleOrNull { t -> t.index == index }
if (index == 0) { if (index == 0) {
text?.text = context.getString(R.string.main_offline) text?.text = context.getString(R.string.main_offline)
description?.text = "" description?.text = ""
} else { } else {
val setting = data.singleOrNull { t -> t.index == index }
text?.text = setting?.name ?: "" text?.text = setting?.name ?: ""
description?.text = setting?.url ?: "" description?.text = setting?.url ?: ""
if (setting == null) serverMenu?.visibility = View.INVISIBLE if (setting == null) serverMenu?.visibility = View.INVISIBLE
@ -95,8 +97,14 @@ internal class ServerRowAdapter(
if (index == 0) { if (index == 0) {
serverMenu?.visibility = View.INVISIBLE serverMenu?.visibility = View.INVISIBLE
image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.screen_on_off)) image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.screen_on_off))
image?.background = ContextCompat.getDrawable(context, R.color.transparent)
} else { } else {
image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.server)) val icon = ContextCompat.getDrawable(context, R.drawable.ic_menu_server_dark)
icon?.setTint(ServerColor.getForegroundColor(context, setting?.color))
image?.setImageDrawable(icon)
val background = ContextCompat.getDrawable(context, R.drawable.circle)
background?.setTint(ServerColor.getBackgroundColor(context, setting?.color))
image?.background = background
} }
// Highlight the Active Server's row by changing its background // Highlight the Active Server's row by changing its background

View File

@ -212,6 +212,7 @@ class ServerSettingsModel(
serverId, serverId,
settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!, settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!,
url, url,
null,
userName, userName,
settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!, settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!,
settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false), settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false),

View File

@ -0,0 +1,26 @@
package org.moire.ultrasonic.util
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import org.moire.ultrasonic.R
object ServerColor {
fun getBackgroundColor(context: Context, serverColor: Int?): Int {
return serverColor ?: ContextCompat.getColor(
context, Util.getResourceFromAttribute(context, R.attr.colorPrimary)
)
}
fun getForegroundColor(context: Context, serverColor: Int?): Int {
if (serverColor == null) return ContextCompat.getColor(
context, Util.getResourceFromAttribute(context, R.attr.colorOnPrimary)
)
val luminance = ColorUtils.calculateLuminance(serverColor)
return if (luminance < 0.25) {
ContextCompat.getColor(context, R.color.selected_menu_dark)
} else {
ContextCompat.getColor(context, R.color.selected_menu_light)
}
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/navigation_header_dark" />
<size
android:width="120dp"
android:height="120dp"/>
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomRightRadius="44dp"
android:bottomLeftRadius="44dp" />
<stroke android:width="2dp" android:color="?attr/colorOnBackground" />
</shape>

View File

@ -14,7 +14,6 @@
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:hint="@string/settings.server_name" a:hint="@string/settings.server_name"
app:layout_constraintBottom_toTopOf="@id/edit_server_address"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -33,7 +32,6 @@
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginBottom="20dp" a:layout_marginBottom="20dp"
a:hint="@string/settings.server_address" a:hint="@string/settings.server_address"
app:layout_constraintBottom_toTopOf="@id/edit_authentication_header"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edit_server_name"> app:layout_constraintTop_toBottomOf="@id/edit_server_name">
@ -46,15 +44,38 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<TextView
a:id="@+id/edit_server_color_text"
style="@style/Widget.AppCompat.CompoundButton.Switch"
a:layout_width="0dp"
a:layout_height="wrap_content"
a:layout_marginStart="5dp"
a:layout_marginLeft="5dp"
a:text="@string/settings.server_color"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/edit_server_color_picker"
app:layout_constraintBottom_toBottomOf="@id/edit_server_color_picker"/>
<ImageView
a:id="@+id/edit_server_color_picker"
a:layout_width="48dp"
a:layout_height="32dp"
a:layout_margin="8dp"
a:src="@drawable/rounded_border"
app:layout_constraintTop_toBottomOf="@id/edit_server_address"
app:layout_constraintRight_toRightOf="parent"
/>
<TextView <TextView
a:id="@+id/edit_authentication_header" a:id="@+id/edit_authentication_header"
style="@style/MenuDrawer.Widget.Category" style="@style/MenuDrawer.Widget.Category"
a:layout_width="wrap_content" a:layout_width="wrap_content"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginTop="15dp"
a:text="@string/server_editor.authentication" a:text="@string/server_editor.authentication"
app:layout_constraintBottom_toTopOf="@id/edit_server_username" app:layout_constraintBottom_toTopOf="@id/edit_server_username"
app:layout_constraintStart_toStartOf="@id/edit_server_username" app:layout_constraintStart_toStartOf="@id/edit_server_username"
app:layout_constraintTop_toBottomOf="@id/edit_server_address" /> app:layout_constraintTop_toBottomOf="@id/edit_server_color_picker" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
a:id="@+id/edit_server_username" a:id="@+id/edit_server_username"

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:a="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:a="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
a:id="@+id/server_layout" a:id="@+id/server_layout"
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
@ -8,36 +11,45 @@
<ImageView <ImageView
a:id="@+id/server_image" a:id="@+id/server_image"
a:layout_width="wrap_content" a:layout_width="48dp"
a:layout_height="match_parent" a:layout_height="48dp"
a:background="@android:color/transparent"
a:focusable="false"
a:layout_centerVertical="true" a:layout_centerVertical="true"
a:paddingHorizontal="15dp" /> a:layout_margin="10dp"
a:background="@drawable/circle"
a:focusable="false"
a:paddingHorizontal="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_menu_server_dark" />
<TextView <TextView
a:id="@+id/server_name" a:id="@+id/server_name"
a:layout_width="wrap_content" a:layout_width="0dp"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginStart="15dp"
a:textAppearance="?android:attr/textAppearanceListItem" a:textAppearance="?android:attr/textAppearanceListItem"
a:paddingTop="10dp" app:layout_constraintStart_toEndOf="@id/server_image"
a:layout_toRightOf="@id/server_image" app:layout_constraintTop_toTopOf="@id/server_image"
a:layout_toEndOf="@id/server_image" /> app:layout_constraintEnd_toStartOf="@id/server_menu"
tools:text="Server name" />
<TextView <TextView
a:id="@+id/server_description" a:id="@+id/server_description"
a:layout_width="wrap_content" a:layout_width="0dp"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginStart="15dp"
a:textAppearance="?android:attr/textAppearanceSmall" a:textAppearance="?android:attr/textAppearanceSmall"
a:layout_below="@id/server_name" app:layout_constraintBottom_toBottomOf="@id/server_image"
a:paddingBottom="10dp" app:layout_constraintStart_toEndOf="@id/server_image"
a:layout_toRightOf="@id/server_image" app:layout_constraintTop_toBottomOf="@id/server_name"
a:layout_toEndOf="@id/server_image" /> app:layout_constraintEnd_toStartOf="@id/server_menu"
tools:text="Server description" />
<ImageButton <ImageButton
a:id="@+id/server_menu" a:id="@+id/server_menu"
a:layout_width="wrap_content" a:layout_width="wrap_content"
a:layout_height="match_parent" a:layout_height="wrap_content"
a:layout_alignParentEnd="true" a:layout_alignParentEnd="true"
a:layout_alignParentRight="true" a:layout_alignParentRight="true"
a:layout_centerVertical="true" a:layout_centerVertical="true"
@ -46,6 +58,10 @@
a:layout_marginRight="15dp" a:layout_marginRight="15dp"
a:focusable="false" a:focusable="false"
a:src="?attr/more_vert" a:src="?attr/more_vert"
tools:src="@drawable/ic_more_vert_dark"
app:layout_constraintTop_toTopOf="@id/server_name"
app:layout_constraintBottom_toBottomOf="@+id/server_description"
app:layout_constraintEnd_toEndOf="parent"
a:padding="5dp" /> a:padding="5dp" />
</RelativeLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -295,6 +295,7 @@
<string name="settings.server_scaling_title">Server-Side Album Art Scaling</string> <string name="settings.server_scaling_title">Server-Side Album Art Scaling</string>
<string name="settings.server_unused">Unused</string> <string name="settings.server_unused">Unused</string>
<string name="settings.server_username">Username</string> <string name="settings.server_username">Username</string>
<string name="settings.server_color">Server color</string>
<string name="settings.show_lockscreen_controls">Show Lock Screen Controls</string> <string name="settings.show_lockscreen_controls">Show Lock Screen Controls</string>
<string name="settings.show_lockscreen_controls_summary">Show playback controls on the lock screen</string> <string name="settings.show_lockscreen_controls_summary">Show playback controls on the lock screen</string>
<string name="settings.show_notification">Show Notification</string> <string name="settings.show_notification">Show Notification</string>