Multiple server addresses (#208)
* Add multiple addresses per server * Clean up * Change icon to globe * Fix AddServerAddressDialog crashing on tv * Fix navigation to main activity on tv * Hide nav bar in UsersFragment and ServerAddressesFragment * Add hint for server address
This commit is contained in:
parent
ebea13777f
commit
6572d7e85b
19 changed files with 539 additions and 71 deletions
|
@ -90,7 +90,7 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
binding.navView!!.visibility = when (destination.id) {
|
||||
R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest -> View.GONE
|
||||
R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest, R.id.usersFragment, R.id.serverAddressesFragment -> View.GONE
|
||||
else -> View.VISIBLE
|
||||
}
|
||||
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar?.title =
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package dev.jdtech.jellyfin.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.jdtech.jellyfin.databinding.ServerAddressListItemBinding
|
||||
import dev.jdtech.jellyfin.models.ServerAddress
|
||||
|
||||
class ServerAddressAdapter(
|
||||
private val clickListener: (address: ServerAddress) -> Unit,
|
||||
private val longClickListener: (address: ServerAddress) -> Boolean
|
||||
) : ListAdapter<ServerAddress, ServerAddressAdapter.ServerAddressViewHolder>(DiffCallback) {
|
||||
class ServerAddressViewHolder(private var binding: ServerAddressListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(address: ServerAddress) {
|
||||
binding.address = address
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<ServerAddress>() {
|
||||
override fun areItemsTheSame(oldItem: ServerAddress, newItem: ServerAddress): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ServerAddress, newItem: ServerAddress): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ServerAddressViewHolder {
|
||||
return ServerAddressViewHolder(
|
||||
ServerAddressListItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ServerAddressViewHolder, position: Int) {
|
||||
val address = getItem(position)
|
||||
holder.itemView.setOnClickListener { clickListener(address) }
|
||||
holder.itemView.setOnLongClickListener { longClickListener(address) }
|
||||
holder.bind(address)
|
||||
}
|
||||
}
|
|
@ -65,9 +65,15 @@ interface ServerDatabaseDao {
|
|||
@Query("delete from users where id = :id")
|
||||
fun deleteUser(id: UUID)
|
||||
|
||||
@Query("delete from serverAddresses where id = :id")
|
||||
fun deleteServerAddress(id: UUID)
|
||||
|
||||
@Query("update servers set currentUserId = :userId where id = :serverId")
|
||||
fun updateServerCurrentUser(serverId: String, userId: UUID)
|
||||
|
||||
@Query("select * from users where id = (select currentUserId from servers where id = :serverId)")
|
||||
fun getServerCurrentUser(serverId: String): User?
|
||||
|
||||
@Query("select * from serverAddresses where id = (select currentServerAddressId from servers where id = :serverId)")
|
||||
fun getServerCurrentAddress(serverId: String): ServerAddress?
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package dev.jdtech.jellyfin.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.app.UiModeManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dev.jdtech.jellyfin.R
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class AddServerAddressDialog(
|
||||
private val viewModel: ServerAddressesViewModel
|
||||
) : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val uiModeManager =
|
||||
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
|
||||
val editText = EditText(this.context)
|
||||
editText.hint = "http://<server_ip>:8096"
|
||||
return activity?.let { activity ->
|
||||
val builder = if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
|
||||
AlertDialog.Builder(activity)
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
}
|
||||
builder
|
||||
.setTitle("Add server address")
|
||||
.setView(editText)
|
||||
.setPositiveButton(getString(R.string.add)) { _, _ ->
|
||||
viewModel.addAddress(editText.text.toString())
|
||||
}
|
||||
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
|
||||
}
|
||||
builder.create()
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package dev.jdtech.jellyfin.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dev.jdtech.jellyfin.R
|
||||
import dev.jdtech.jellyfin.models.ServerAddress
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class DeleteServerAddressDialog(
|
||||
private val viewModel: ServerAddressesViewModel,
|
||||
val address: ServerAddress
|
||||
) : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
val builder = MaterialAlertDialogBuilder(it)
|
||||
builder.setTitle("Remove server address")
|
||||
.setMessage("Are you sure you want to remove the server addres? ${address.address}")
|
||||
.setPositiveButton(getString(R.string.remove)) { _, _ ->
|
||||
viewModel.deleteAddress(address)
|
||||
}
|
||||
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
|
||||
}
|
||||
builder.create()
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package dev.jdtech.jellyfin.fragments
|
||||
|
||||
import android.app.UiModeManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.jdtech.jellyfin.adapters.ServerAddressAdapter
|
||||
import dev.jdtech.jellyfin.databinding.FragmentServerAddressesBinding
|
||||
import dev.jdtech.jellyfin.dialogs.AddServerAddressDialog
|
||||
import dev.jdtech.jellyfin.dialogs.DeleteServerAddressDialog
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ServerAddressesFragment : Fragment() {
|
||||
|
||||
private lateinit var binding: FragmentServerAddressesBinding
|
||||
private lateinit var uiModeManager: UiModeManager
|
||||
private val viewModel: ServerAddressesViewModel by viewModels()
|
||||
private val args: UsersFragmentArgs by navArgs()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentServerAddressesBinding.inflate(inflater)
|
||||
uiModeManager =
|
||||
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
|
||||
|
||||
binding.addressesRecyclerView.adapter =
|
||||
ServerAddressAdapter(
|
||||
{ address ->
|
||||
viewModel.switchToAddress(address)
|
||||
},
|
||||
{ address ->
|
||||
DeleteServerAddressDialog(viewModel, address).show(
|
||||
parentFragmentManager,
|
||||
"deleteServerAddress"
|
||||
)
|
||||
true
|
||||
}
|
||||
)
|
||||
|
||||
binding.buttonAddAddress.setOnClickListener {
|
||||
AddServerAddressDialog(viewModel).show(
|
||||
parentFragmentManager,
|
||||
"addServerAddress"
|
||||
)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToMain.collect {
|
||||
if (it) {
|
||||
navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.uiState.collect { uiState ->
|
||||
Timber.d("$uiState")
|
||||
when (uiState) {
|
||||
is ServerAddressesViewModel.UiState.Normal -> bindUiStateNormal(uiState)
|
||||
is ServerAddressesViewModel.UiState.Loading -> Unit
|
||||
is ServerAddressesViewModel.UiState.Error -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.loadAddresses(args.serverId)
|
||||
}
|
||||
|
||||
fun bindUiStateNormal(uiState: ServerAddressesViewModel.UiState.Normal) {
|
||||
(binding.addressesRecyclerView.adapter as ServerAddressAdapter).submitList(uiState.addresses)
|
||||
}
|
||||
|
||||
private fun navigateToMainActivity() {
|
||||
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
|
||||
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragmentTv())
|
||||
} else {
|
||||
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("switchAddress")?.setOnPreferenceClickListener {
|
||||
val serverId = appPreferences.currentServer!!
|
||||
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToServerAddressesFragment(serverId))
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("privacyPolicy")?.setOnPreferenceClickListener {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package dev.jdtech.jellyfin.fragments
|
||||
|
||||
import android.app.UiModeManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -24,6 +27,7 @@ import timber.log.Timber
|
|||
class UsersFragment : Fragment() {
|
||||
|
||||
private lateinit var binding: FragmentUsersBinding
|
||||
private lateinit var uiModeManager: UiModeManager
|
||||
private val viewModel: UsersViewModel by viewModels()
|
||||
private val args: UsersFragmentArgs by navArgs()
|
||||
|
||||
|
@ -33,10 +37,8 @@ class UsersFragment : Fragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentUsersBinding.inflate(inflater)
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
binding.viewModel = viewModel
|
||||
uiModeManager =
|
||||
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
|
||||
|
||||
binding.usersRecyclerView.adapter =
|
||||
UserListAdapter(
|
||||
|
@ -99,6 +101,10 @@ class UsersFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun navigateToMainActivity() {
|
||||
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
|
||||
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
|
||||
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragmentTv())
|
||||
} else {
|
||||
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,6 @@ data class Server(
|
|||
@PrimaryKey
|
||||
val id: String,
|
||||
val name: String,
|
||||
val currentServerAddressId: UUID?,
|
||||
var currentServerAddressId: UUID?,
|
||||
var currentUserId: UUID?,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package dev.jdtech.jellyfin.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.models.ServerAddress
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel
|
||||
class ServerAddressesViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val jellyfinApi: JellyfinApi,
|
||||
private val database: ServerDatabaseDao,
|
||||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
sealed class UiState {
|
||||
data class Normal(val addresses: List<ServerAddress>) : UiState()
|
||||
object Loading : UiState()
|
||||
data class Error(val error: Exception) : UiState()
|
||||
}
|
||||
|
||||
private val _navigateToMain = MutableSharedFlow<Boolean>()
|
||||
val navigateToMain = _navigateToMain.asSharedFlow()
|
||||
|
||||
private var currentServerId: String = ""
|
||||
|
||||
fun loadAddresses(serverId: String) {
|
||||
currentServerId = serverId
|
||||
viewModelScope.launch {
|
||||
_uiState.emit(UiState.Loading)
|
||||
try {
|
||||
val serverWithUser = database.getServerWithAddresses(serverId)
|
||||
_uiState.emit(UiState.Normal(serverWithUser.addresses))
|
||||
} catch (e: Exception) {
|
||||
_uiState.emit(UiState.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete server address from database
|
||||
*
|
||||
* @param address The server address
|
||||
*/
|
||||
fun deleteAddress(address: ServerAddress) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val currentAddress = database.getServerCurrentAddress(currentServerId)
|
||||
if (address == currentAddress) {
|
||||
Timber.e("You cannot delete the current address")
|
||||
return@launch
|
||||
}
|
||||
database.deleteServerAddress(address.id)
|
||||
loadAddresses(currentServerId)
|
||||
}
|
||||
}
|
||||
|
||||
fun switchToAddress(address: ServerAddress) {
|
||||
viewModelScope.launch {
|
||||
val server = database.get(currentServerId) ?: return@launch
|
||||
server.currentServerAddressId = address.id
|
||||
database.update(server)
|
||||
|
||||
jellyfinApi.api.baseUrl = address.address
|
||||
|
||||
_navigateToMain.emit(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun addAddress(address: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val serverAddress = ServerAddress(UUID.randomUUID(), currentServerId, address)
|
||||
database.insertServerAddress(serverAddress)
|
||||
loadAddresses(currentServerId)
|
||||
}
|
||||
}
|
||||
}
|
28
app/src/main/res/drawable/ic_globe.xml
Normal file
28
app/src/main/res/drawable/ic_globe.xml
Normal file
|
@ -0,0 +1,28 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M2,12L22,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,2a15.3,15.3 0,0 1,4 10,15.3 15.3,0 0,1 -4,10 15.3,15.3 0,0 1,-4 -10,15.3 15.3,0 0,1 4,-10z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@android:color/white"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +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/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/addresses_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/server_address_list_item" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_add_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_address"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,39 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="dev.jdtech.jellyfin.viewmodels.UsersViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_content"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/users_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/user_list_item" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/users_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/user_list_item" />
|
||||
<Button
|
||||
android:id="@+id/button_add_user"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_user"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_add_user"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_user"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
29
app/src/main/res/layout/fragment_server_addresses.xml
Normal file
29
app/src/main/res/layout/fragment_server_addresses.xml
Normal file
|
@ -0,0 +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/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/addresses_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/server_address_list_item" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/button_add_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_address"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,39 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="dev.jdtech.jellyfin.viewmodels.UsersViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/main_content"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/users_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/user_list_item" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/users_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/user_list_item" />
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/button_add_user"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_user"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/button_add_user"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/add_user"
|
||||
app:icon="@drawable/ic_plus" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
34
app/src/main/res/layout/server_address_list_item.xml
Normal file
34
app/src/main/res/layout/server_address_list_item.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="address"
|
||||
type="dev.jdtech.jellyfin.models.ServerAddress" />
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="@drawable/ripple_background">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{address.address}"
|
||||
android:layout_margin="12dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
tools:text="https://..." />
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
|
@ -99,6 +99,9 @@
|
|||
<action
|
||||
android:id="@+id/action_navigation_settings_to_usersFragment"
|
||||
app:destination="@id/usersFragment" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_settings_to_serverAddressesFragment"
|
||||
app:destination="@id/serverAddressesFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_about_libraries"
|
||||
app:destination="@id/about_libraries" />
|
||||
|
@ -418,4 +421,24 @@
|
|||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/serverAddressesFragment"
|
||||
android:name="dev.jdtech.jellyfin.fragments.ServerAddressesFragment"
|
||||
android:label="@string/addresses"
|
||||
tools:layout="@layout/fragment_server_addresses">
|
||||
<action
|
||||
android:id="@+id/action_usersFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_usersFragment_to_homeFragmentTv"
|
||||
app:destination="@id/homeFragmentTv"
|
||||
app:popUpTo="@id/homeFragmentTv"
|
||||
app:popUpToInclusive="true" />
|
||||
<argument
|
||||
android:name="serverId"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
|
@ -149,4 +149,8 @@
|
|||
<string name="pref_player_mpv_vo">Video output</string>
|
||||
<string name="pref_player_mpv_ao">Audio output</string>
|
||||
<string name="pref_player_mpv_gpu_api" translatable="false">GPU API</string>
|
||||
<string name="addresses">Addresses</string>
|
||||
<string name="add_address">Add address</string>
|
||||
<string name="add_server_address">Add server address</string>
|
||||
<string name="add">Add</string>
|
||||
</resources>
|
|
@ -16,6 +16,11 @@
|
|||
app:key="switchUser"
|
||||
app:title="@string/users" />
|
||||
|
||||
<Preference
|
||||
app:icon="@drawable/ic_globe"
|
||||
app:key="switchAddress"
|
||||
app:title="@string/addresses" />
|
||||
|
||||
<Preference
|
||||
app:fragment="dev.jdtech.jellyfin.fragments.SettingsAppearanceFragment"
|
||||
app:icon="@drawable/ic_palette"
|
||||
|
|
Loading…
Reference in a new issue