diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0c463e68..9bbe2e50 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="dev.jdtech.jellyfin">
+
diff --git a/app/src/main/java/dev/jdtech/jellyfin/adapters/DiscoveredServerListAdapter.kt b/app/src/main/java/dev/jdtech/jellyfin/adapters/DiscoveredServerListAdapter.kt
new file mode 100644
index 00000000..f057edcb
--- /dev/null
+++ b/app/src/main/java/dev/jdtech/jellyfin/adapters/DiscoveredServerListAdapter.kt
@@ -0,0 +1,58 @@
+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.DiscoveredServerItemBinding
+import dev.jdtech.jellyfin.models.DiscoveredServer
+
+class DiscoveredServerListAdapter(
+ private val clickListener: (server: DiscoveredServer) -> Unit) :
+ ListAdapter(
+ DiffCallback
+ ) {
+ class DiscoveredServerViewHolder(private var binding: DiscoveredServerItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun bind(server: DiscoveredServer) {
+ binding.server = server
+ binding.executePendingBindings()
+ }
+ }
+
+ companion object DiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: DiscoveredServer,
+ newItem: DiscoveredServer
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: DiscoveredServer,
+ newItem: DiscoveredServer
+ ): Boolean {
+ return oldItem == newItem
+ }
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): DiscoveredServerViewHolder {
+ return DiscoveredServerViewHolder(
+ DiscoveredServerItemBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ )
+ }
+
+ override fun onBindViewHolder(holder: DiscoveredServerViewHolder, position: Int) {
+ val server = getItem(position)
+ holder.itemView.setOnClickListener { clickListener(server) }
+ holder.bind(server)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
index 5adf1656..200bedab 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
@@ -17,6 +17,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
+import dev.jdtech.jellyfin.adapters.DiscoveredServerListAdapter
import dev.jdtech.jellyfin.databinding.FragmentAddServerBinding
import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
import kotlinx.coroutines.launch
@@ -51,6 +52,11 @@ class AddServerFragment : Fragment() {
connectToServer()
}
+ binding.serversRecyclerView.adapter = DiscoveredServerListAdapter { server ->
+ (binding.editTextServerAddress as AppCompatEditText).setText(server.address)
+ connectToServer()
+ }
+
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
@@ -64,6 +70,17 @@ class AddServerFragment : Fragment() {
}
}
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.discoveredServersState.collect { serversState ->
+ when (serversState) {
+ is AddServerViewModel.DiscoveredServersState.Loading -> Unit
+ is AddServerViewModel.DiscoveredServersState.Servers -> bindDiscoveredServersStateServers(serversState)
+ }
+ }
+ }
+ }
+
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToLogin.collect {
@@ -102,6 +119,16 @@ class AddServerFragment : Fragment() {
}
}
+ private fun bindDiscoveredServersStateServers(serversState: AddServerViewModel.DiscoveredServersState.Servers) {
+ val servers = serversState.servers
+ if (servers.isEmpty()) {
+ binding.serversRecyclerView.isVisible = false
+ } else {
+ binding.serversRecyclerView.isVisible = true
+ (binding.serversRecyclerView.adapter as DiscoveredServerListAdapter).submitList(servers)
+ }
+ }
+
private fun connectToServer() {
val serverAddress = (binding.editTextServerAddress as AppCompatEditText).text.toString()
viewModel.checkServer(serverAddress.removeSuffix("/"))
diff --git a/app/src/main/java/dev/jdtech/jellyfin/models/DiscoveredServer.kt b/app/src/main/java/dev/jdtech/jellyfin/models/DiscoveredServer.kt
new file mode 100644
index 00000000..acc548c3
--- /dev/null
+++ b/app/src/main/java/dev/jdtech/jellyfin/models/DiscoveredServer.kt
@@ -0,0 +1,7 @@
+package dev.jdtech.jellyfin.models
+
+data class DiscoveredServer(
+ val id: String,
+ val name: String,
+ val address: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
index 0008ba7d..2e25a85b 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
@@ -10,6 +10,7 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao
+import dev.jdtech.jellyfin.models.DiscoveredServer
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.jellyfin.sdk.discovery.RecommendedServerInfo
@@ -32,7 +33,10 @@ constructor(
val uiState = _uiState.asStateFlow()
private val _navigateToLogin = MutableSharedFlow()
val navigateToLogin = _navigateToLogin.asSharedFlow()
+ private val _discoveredServersState = MutableStateFlow(DiscoveredServersState.Loading)
+ val discoveredServersState = _discoveredServersState.asStateFlow()
+ private val discoveredServers = mutableListOf()
private var serverFound = false
sealed class UiState {
@@ -41,6 +45,27 @@ constructor(
data class Error(val message: String) : UiState()
}
+ sealed class DiscoveredServersState {
+ object Loading : DiscoveredServersState()
+ data class Servers(val servers: List) : DiscoveredServersState()
+ }
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ val servers = jellyfinApi.jellyfin.discovery.discoverLocalServers()
+ servers.collect { serverDiscoveryInfo ->
+ discoveredServers.add(DiscoveredServer(
+ serverDiscoveryInfo.id,
+ serverDiscoveryInfo.name,
+ serverDiscoveryInfo.address
+ ))
+ _discoveredServersState.emit(
+ DiscoveredServersState.Servers(discoveredServers)
+ )
+ }
+ }
+ }
+
/**
* Run multiple check on the server before continuing:
*
diff --git a/app/src/main/res/layout-television/discovered_server_item.xml b/app/src/main/res/layout-television/discovered_server_item.xml
new file mode 100644
index 00000000..b0432b86
--- /dev/null
+++ b/app/src/main/res/layout-television/discovered_server_item.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/fragment_add_server.xml b/app/src/main/res/layout-television/fragment_add_server.xml
index 81cb1302..b9184b12 100644
--- a/app/src/main/res/layout-television/fragment_add_server.xml
+++ b/app/src/main/res/layout-television/fragment_add_server.xml
@@ -26,8 +26,6 @@
android:id="@+id/linearLayout"
android:layout_width="@dimen/setup_container_width"
android:layout_height="wrap_content"
- android:layout_marginStart="24dp"
- android:layout_marginEnd="24dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -39,15 +37,31 @@
android:id="@+id/text_add_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
android:layout_marginBottom="32dp"
android:text="@string/add_server"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="?android:textColorPrimary" />
+
+
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp">