mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
feat: rebuild custom instances dialogs for better UX
This commit is contained in:
parent
71f0d48e24
commit
bac80715e3
@ -59,4 +59,5 @@ object IntentData {
|
|||||||
const val segments = "segments"
|
const val segments = "segments"
|
||||||
const val alreadyStarted = "alreadyStarted"
|
const val alreadyStarted = "alreadyStarted"
|
||||||
const val showUpcoming = "showUpcoming"
|
const val showUpcoming = "showUpcoming"
|
||||||
|
const val customInstance = "customInstance"
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ object PreferenceKeys {
|
|||||||
const val AUTH_INSTANCE = "selectAuthInstance"
|
const val AUTH_INSTANCE = "selectAuthInstance"
|
||||||
const val AUTH_INSTANCE_TOGGLE = "auth_instance_toggle"
|
const val AUTH_INSTANCE_TOGGLE = "auth_instance_toggle"
|
||||||
const val CUSTOM_INSTANCE = "customInstance"
|
const val CUSTOM_INSTANCE = "customInstance"
|
||||||
const val CLEAR_CUSTOM_INSTANCES = "clearCustomInstances"
|
|
||||||
const val LOGIN_REGISTER = "login_register"
|
const val LOGIN_REGISTER = "login_register"
|
||||||
const val LOGOUT = "logout"
|
const val LOGOUT = "logout"
|
||||||
const val DELETE_ACCOUNT = "delete_account"
|
const val DELETE_ACCOUNT = "delete_account"
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
package com.github.libretube.db.dao
|
package com.github.libretube.db.dao
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import com.github.libretube.db.obj.CustomInstance
|
import com.github.libretube.db.obj.CustomInstance
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface CustomInstanceDao {
|
interface CustomInstanceDao {
|
||||||
@Query("SELECT * FROM customInstance")
|
@Query("SELECT * FROM customInstance ORDER BY name")
|
||||||
suspend fun getAll(): List<CustomInstance>
|
suspend fun getAll(): List<CustomInstance>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM customInstance ORDER BY name")
|
||||||
|
fun getAllFlow(): Flow<List<CustomInstance>>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(customInstance: CustomInstance)
|
suspend fun insert(customInstance: CustomInstance)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertAll(customInstances: List<CustomInstance>)
|
suspend fun insertAll(customInstances: List<CustomInstance>)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM customInstance WHERE apiUrl = :apiUrl")
|
||||||
|
suspend fun getByApiUrl(apiUrl: String): CustomInstance?
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteCustomInstance(customInstance: CustomInstance)
|
||||||
|
|
||||||
@Query("DELETE FROM customInstance")
|
@Query("DELETE FROM customInstance")
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
package com.github.libretube.db.obj
|
package com.github.libretube.db.obj
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@Entity(tableName = "customInstance")
|
@Entity(tableName = "customInstance")
|
||||||
|
@Parcelize
|
||||||
class CustomInstance(
|
class CustomInstance(
|
||||||
@PrimaryKey var name: String = "",
|
@PrimaryKey var name: String = "",
|
||||||
@ColumnInfo var apiUrl: String = "",
|
@ColumnInfo var apiUrl: String = "",
|
||||||
@ColumnInfo var frontendUrl: String = ""
|
@ColumnInfo var frontendUrl: String = ""
|
||||||
)
|
) : Parcelable
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.github.libretube.ui.adapters
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import com.github.libretube.databinding.CustomInstanceRowBinding
|
||||||
|
import com.github.libretube.db.obj.CustomInstance
|
||||||
|
import com.github.libretube.ui.adapters.callbacks.DiffUtilItemCallback
|
||||||
|
import com.github.libretube.ui.viewholders.CustomInstancesViewHolder
|
||||||
|
|
||||||
|
class CustomInstancesAdapter(
|
||||||
|
private val onClickInstance: (CustomInstance) -> Unit,
|
||||||
|
private val onDeleteInstance: (CustomInstance) -> Unit
|
||||||
|
) : ListAdapter<CustomInstance, CustomInstancesViewHolder>(
|
||||||
|
DiffUtilItemCallback()
|
||||||
|
) {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomInstancesViewHolder {
|
||||||
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
|
val binding = CustomInstanceRowBinding.inflate(layoutInflater, parent, false)
|
||||||
|
return CustomInstancesViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: CustomInstancesViewHolder, position: Int) {
|
||||||
|
val instance = getItem(position)!!
|
||||||
|
|
||||||
|
with (holder.binding) {
|
||||||
|
instanceName.text = instance.name
|
||||||
|
|
||||||
|
root.setOnClickListener {
|
||||||
|
onClickInstance(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteInstance.setOnClickListener {
|
||||||
|
onDeleteInstance.invoke(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.github.libretube.ui.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.constants.IntentData
|
||||||
|
import com.github.libretube.databinding.DialogCustomInstanceBinding
|
||||||
|
import com.github.libretube.db.obj.CustomInstance
|
||||||
|
import com.github.libretube.extensions.parcelable
|
||||||
|
import com.github.libretube.extensions.toastFromMainThread
|
||||||
|
import com.github.libretube.ui.models.CustomInstancesModel
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
|
||||||
|
class CreateCustomInstanceDialog : DialogFragment() {
|
||||||
|
val viewModel: CustomInstancesModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val binding = DialogCustomInstanceBinding.inflate(layoutInflater)
|
||||||
|
arguments?.parcelable<CustomInstance>(IntentData.customInstance)?.let { initialInstance ->
|
||||||
|
binding.instanceName.setText(initialInstance.name)
|
||||||
|
binding.instanceApiUrl.setText(initialInstance.apiUrl)
|
||||||
|
binding.instanceFrontendUrl.setText(initialInstance.frontendUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.instanceApiUrl.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (hasFocus || !binding.instanceName.text.isNullOrEmpty()) return@setOnFocusChangeListener
|
||||||
|
|
||||||
|
// automatically set the api name
|
||||||
|
val apiUrl = binding.instanceApiUrl.text.toString().toHttpUrlOrNull()
|
||||||
|
if (apiUrl != null) {
|
||||||
|
binding.instanceName.setText(apiUrl.host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.customInstance)
|
||||||
|
.setView(binding.root)
|
||||||
|
.setPositiveButton(R.string.addInstance, null)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
.apply {
|
||||||
|
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val instanceName = binding.instanceName.text.toString()
|
||||||
|
val apiUrl = binding.instanceApiUrl.text.toString()
|
||||||
|
val frontendUrl = binding.instanceFrontendUrl.text.toString()
|
||||||
|
|
||||||
|
try {
|
||||||
|
viewModel.addCustomInstance(apiUrl, instanceName, frontendUrl)
|
||||||
|
requireDialog().dismiss()
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
context.toastFromMainThread(R.string.empty_instance)
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
context.toastFromMainThread(R.string.invalid_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
package com.github.libretube.ui.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.github.libretube.R
|
|
||||||
import com.github.libretube.databinding.DialogCustomInstanceBinding
|
|
||||||
import com.github.libretube.db.DatabaseHolder.Database
|
|
||||||
import com.github.libretube.db.obj.CustomInstance
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
|
|
||||||
class CustomInstanceDialog : DialogFragment() {
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val binding = DialogCustomInstanceBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.customInstance)
|
|
||||||
.setView(binding.root)
|
|
||||||
.setPositiveButton(R.string.addInstance) { _, _ ->
|
|
||||||
val instanceName = binding.instanceName.text.toString()
|
|
||||||
val apiUrl = binding.instanceApiUrl.text.toString()
|
|
||||||
val frontendUrl = binding.instanceFrontendUrl.text.toString()
|
|
||||||
|
|
||||||
if (instanceName.isNotEmpty() && apiUrl.isNotEmpty() && frontendUrl.isNotEmpty()) {
|
|
||||||
if (apiUrl.toHttpUrlOrNull() != null && frontendUrl.toHttpUrlOrNull() != null) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Database.customInstanceDao()
|
|
||||||
.insert(CustomInstance(instanceName, apiUrl, frontendUrl))
|
|
||||||
ActivityCompat.recreate(requireActivity())
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), R.string.invalid_url, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// at least one empty input
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.empty_instance,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.github.libretube.ui.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.constants.IntentData
|
||||||
|
import com.github.libretube.databinding.DialogCustomIntancesListBinding
|
||||||
|
import com.github.libretube.ui.adapters.CustomInstancesAdapter
|
||||||
|
import com.github.libretube.ui.models.CustomInstancesModel
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CustomInstancesListDialog: DialogFragment() {
|
||||||
|
val viewModel: CustomInstancesModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val binding = DialogCustomIntancesListBinding.inflate(layoutInflater)
|
||||||
|
val adapter = CustomInstancesAdapter(
|
||||||
|
onClickInstance = {
|
||||||
|
CreateCustomInstanceDialog()
|
||||||
|
.apply {
|
||||||
|
arguments = bundleOf(IntentData.customInstance to it)
|
||||||
|
}
|
||||||
|
.show(childFragmentManager, null)
|
||||||
|
},
|
||||||
|
onDeleteInstance = {
|
||||||
|
viewModel.deleteCustomInstance(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
binding.customInstancesRecycler.adapter = adapter
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.instances.collectLatest {
|
||||||
|
adapter.submitList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(getString(R.string.customInstance))
|
||||||
|
.setView(binding.root)
|
||||||
|
.setPositiveButton(getString(R.string.okay), null)
|
||||||
|
.setNegativeButton(getString(R.string.addInstance), null)
|
||||||
|
.show()
|
||||||
|
.apply {
|
||||||
|
getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener {
|
||||||
|
CreateCustomInstanceDialog()
|
||||||
|
.show(childFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -118,12 +118,12 @@ class ShareDialog : DialogFragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// get the api urls of the other custom instances
|
// get the api urls of the other custom instances
|
||||||
val customInstances = runBlocking(Dispatchers.IO) {
|
val customInstance = runBlocking(Dispatchers.IO) {
|
||||||
Database.customInstanceDao().getAll()
|
Database.customInstanceDao().getByApiUrl(instancePref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the custom instance frontend url if available
|
// return the custom instance frontend url if available
|
||||||
return customInstances.firstOrNull { it.apiUrl == instancePref }?.frontendUrl.orEmpty()
|
return customInstance?.frontendUrl.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateLinkText(binding: DialogShareBinding, customInstanceUrl: HttpUrl?): String {
|
private fun generateLinkText(binding: DialogShareBinding, customInstanceUrl: HttpUrl?): String {
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.github.libretube.ui.models
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
|
import com.github.libretube.db.obj.CustomInstance
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
|
||||||
|
class CustomInstancesModel: ViewModel() {
|
||||||
|
val instances = Database.customInstanceDao().getAllFlow()
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun addCustomInstance(apiUrlInput: String, instanceNameInput: String?, frontendUrlInput: String?) {
|
||||||
|
if (apiUrlInput.isEmpty()) throw IllegalArgumentException()
|
||||||
|
|
||||||
|
val apiUrl = apiUrlInput.toHttpUrlOrNull() ?: throw MalformedURLException()
|
||||||
|
val frontendUrl = if (!frontendUrlInput.isNullOrBlank()) {
|
||||||
|
frontendUrlInput.toHttpUrlOrNull() ?: throw MalformedURLException()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val instanceName = instanceNameInput ?: apiUrl.host
|
||||||
|
|
||||||
|
Database.customInstanceDao()
|
||||||
|
.insert(CustomInstance(instanceName, apiUrl.toString(), frontendUrl?.toString().orEmpty()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCustomInstance(customInstance: CustomInstance) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
Database.customInstanceDao().deleteCustomInstance(customInstance)
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,8 @@ import com.github.libretube.extensions.toastFromMainDispatcher
|
|||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.github.libretube.ui.adapters.InstancesAdapter
|
import com.github.libretube.ui.adapters.InstancesAdapter
|
||||||
import com.github.libretube.ui.base.BasePreferenceFragment
|
import com.github.libretube.ui.base.BasePreferenceFragment
|
||||||
import com.github.libretube.ui.dialogs.CustomInstanceDialog
|
import com.github.libretube.ui.dialogs.CreateCustomInstanceDialog
|
||||||
|
import com.github.libretube.ui.dialogs.CustomInstancesListDialog
|
||||||
import com.github.libretube.ui.dialogs.DeleteAccountDialog
|
import com.github.libretube.ui.dialogs.DeleteAccountDialog
|
||||||
import com.github.libretube.ui.dialogs.LoginDialog
|
import com.github.libretube.ui.dialogs.LoginDialog
|
||||||
import com.github.libretube.ui.dialogs.LogoutDialog
|
import com.github.libretube.ui.dialogs.LogoutDialog
|
||||||
@ -82,17 +83,8 @@ class InstanceSettings : BasePreferenceFragment() {
|
|||||||
|
|
||||||
val customInstance = findPreference<Preference>(PreferenceKeys.CUSTOM_INSTANCE)
|
val customInstance = findPreference<Preference>(PreferenceKeys.CUSTOM_INSTANCE)
|
||||||
customInstance?.setOnPreferenceClickListener {
|
customInstance?.setOnPreferenceClickListener {
|
||||||
CustomInstanceDialog()
|
CustomInstancesListDialog()
|
||||||
.show(childFragmentManager, CustomInstanceDialog::class.java.name)
|
.show(childFragmentManager, CreateCustomInstanceDialog::class.java.name)
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val clearCustomInstances = findPreference<Preference>(PreferenceKeys.CLEAR_CUSTOM_INSTANCES)
|
|
||||||
clearCustomInstances?.setOnPreferenceClickListener {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Database.customInstanceDao().deleteAll()
|
|
||||||
ActivityCompat.recreate(requireActivity())
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.github.libretube.ui.viewholders
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.github.libretube.databinding.CustomInstanceRowBinding
|
||||||
|
|
||||||
|
class CustomInstancesViewHolder(
|
||||||
|
val binding: CustomInstanceRowBinding
|
||||||
|
): RecyclerView.ViewHolder(binding.root)
|
31
app/src/main/res/layout/custom_instance_row.xml
Normal file
31
app/src/main/res/layout/custom_instance_row.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables"
|
||||||
|
android:background="@drawable/rounded_ripple"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="2dp"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingVertical="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/instanceName"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/deleteInstance"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_delete" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -5,6 +5,17 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="8dp">
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/CustomDialogTextInputLayout"
|
||||||
|
android:hint="@string/instance_api_url">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/instanceApiUrl"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/CustomDialogTextInputLayout"
|
style="@style/CustomDialogTextInputLayout"
|
||||||
android:hint="@string/instance_name">
|
android:hint="@string/instance_name">
|
||||||
@ -27,15 +38,4 @@
|
|||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
style="@style/CustomDialogTextInputLayout"
|
|
||||||
android:hint="@string/instance_api_url">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/instanceApiUrl"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="text" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
10
app/src/main/res/layout/dialog_custom_intances_list.xml
Normal file
10
app/src/main/res/layout/dialog_custom_intances_list.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:id="@+id/custom_instances_recycler"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
@ -176,7 +176,7 @@
|
|||||||
<string name="resume">Resume</string>
|
<string name="resume">Resume</string>
|
||||||
<string name="stop">Stop</string>
|
<string name="stop">Stop</string>
|
||||||
<string name="player_autoplay">Autoplay</string>
|
<string name="player_autoplay">Autoplay</string>
|
||||||
<string name="instance_frontend_url">URL to instance frontend</string>
|
<string name="instance_frontend_url">URL to instance frontend (optional)</string>
|
||||||
<string name="quality">Quality</string>
|
<string name="quality">Quality</string>
|
||||||
<string name="behavior">Behavior</string>
|
<string name="behavior">Behavior</string>
|
||||||
<string name="player_summary">Defaults and behavior</string>
|
<string name="player_summary">Defaults and behavior</string>
|
||||||
|
@ -40,12 +40,6 @@
|
|||||||
app:summary="@string/customInstance_summary"
|
app:summary="@string/customInstance_summary"
|
||||||
app:title="@string/customInstance" />
|
app:title="@string/customInstance" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:icon="@drawable/ic_trash"
|
|
||||||
app:key="clearCustomInstances"
|
|
||||||
android:dependency="full_local_mode"
|
|
||||||
app:title="@string/clear_customInstances" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:icon="@drawable/ic_server"
|
android:icon="@drawable/ic_server"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user