Several minor bug fixes (#1811)
## Description Fixes several small bugs and does some minor refactoring: - Use "Recently Added in XYZ" instead of "Recently recorded" for recorded tv libraries - Improve home page query performance by using fewer fields in the query - When an error occurs connecting to discovered servers, show the error instead of only logging - Fix headers sometimes appearing briefly on some pages when navigating back - Catch exceptions in socket connections - Use a larger buffer when downloading updates - Split up ADB logging for media info ### Related issues Related to https://github.com/damontecres/Wholphin/issues/1517#issuecomment-5184316086 Maybe the larger download buffer will help with https://github.com/damontecres/Wholphin/issues/1696 Will help show the error in #1801 Prevents the crash in #1786 ### Testing Emulator & nvidia shield ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
54966f9124
commit
51ab39ed3b
17 changed files with 282 additions and 250 deletions
|
|
@ -49,8 +49,6 @@ const val DEFAULT_PAGE_SIZE = 100
|
|||
*/
|
||||
val DefaultItemFields =
|
||||
listOf(
|
||||
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
|
||||
ItemFields.SEASON_USER_DATA,
|
||||
ItemFields.CHILD_COUNT,
|
||||
ItemFields.OVERVIEW,
|
||||
ItemFields.TRICKPLAY,
|
||||
|
|
@ -58,7 +56,6 @@ val DefaultItemFields =
|
|||
ItemFields.CHAPTERS,
|
||||
ItemFields.MEDIA_SOURCES,
|
||||
ItemFields.MEDIA_SOURCE_COUNT,
|
||||
ItemFields.PARENT_ID,
|
||||
ItemFields.CAN_DELETE,
|
||||
)
|
||||
|
||||
|
|
@ -67,13 +64,16 @@ val DefaultItemFields =
|
|||
*/
|
||||
val SlimItemFields =
|
||||
listOf(
|
||||
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
|
||||
ItemFields.SEASON_USER_DATA,
|
||||
ItemFields.CHILD_COUNT,
|
||||
ItemFields.OVERVIEW,
|
||||
ItemFields.SORT_NAME,
|
||||
ItemFields.MEDIA_SOURCE_COUNT,
|
||||
ItemFields.PARENT_ID,
|
||||
ItemFields.CAN_DELETE,
|
||||
)
|
||||
|
||||
val HomeItemFields =
|
||||
listOf(
|
||||
ItemFields.OVERVIEW,
|
||||
ItemFields.CAN_DELETE,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ fun ItemCardImageOverlay(
|
|||
}
|
||||
}
|
||||
|
||||
watchedPercent?.let { percent ->
|
||||
if (watchedPercent != null && watchedPercent > 0 && watchedPercent < 100) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -274,7 +274,7 @@ fun ItemCardImageOverlay(
|
|||
MaterialTheme.colorScheme.tertiary,
|
||||
).clip(RectangleShape)
|
||||
.height(Cards.playedPercentHeight)
|
||||
.fillMaxWidth((percent / 100.0).toFloat()),
|
||||
.fillMaxWidth((watchedPercent / 100.0).toFloat()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -99,7 +100,7 @@ fun CollectionFolderList(
|
|||
val positionFocusRequester = remember { FocusRequester() }
|
||||
val showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME
|
||||
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
fun jumpTo(newPosition: Int) {
|
||||
scope.launch {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -32,7 +33,7 @@ fun CollectionFolderGeneric(
|
|||
sortOptions: List<ItemSortBy> = VideoSortOptions,
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
val viewOptions =
|
||||
remember(usePosters) {
|
||||
if (usePosters) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -48,7 +49,7 @@ fun CollectionFolderPhotoAlbum(
|
|||
)
|
||||
},
|
||||
) {
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
CollectionFolderView(
|
||||
preferences = preferences,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.github.damontecres.wholphin.ui.detail
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -24,7 +24,7 @@ fun CollectionFolderPlaylist(
|
|||
filter: CollectionFolderFilter = CollectionFolderFilter(),
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
CollectionFolderView(
|
||||
preferences = preferences,
|
||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.github.damontecres.wholphin.ui.detail
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -24,7 +24,7 @@ fun CollectionFolderRecordings(
|
|||
filter: CollectionFolderFilter = CollectionFolderFilter(),
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
CollectionFolderView(
|
||||
preferences = preferences,
|
||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
|
|
@ -259,7 +260,7 @@ fun DiscoverRequestGrid(
|
|||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
var showHeader by remember { mutableStateOf(state.startIndex < 6) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(state.startIndex < 6) }
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -576,7 +576,7 @@ fun SearchPage(
|
|||
viewModel.navigationManager.navigateTo(dest)
|
||||
}
|
||||
|
||||
var showHeader by remember { mutableStateOf(true) }
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
val positionCallback = { columns: Int, index: Int ->
|
||||
showHeader = index < columns
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.github.damontecres.wholphin.services.NavDrawerService
|
|||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.services.UnsupportedHomeSettingsVersionException
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.services.getRecentlyAddedTitle
|
||||
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
|
||||
import com.github.damontecres.wholphin.services.tvAccess
|
||||
import com.github.damontecres.wholphin.ui.AspectRatio
|
||||
|
|
@ -381,7 +382,7 @@ class HomeSettingsViewModel
|
|||
}
|
||||
|
||||
LibraryRowType.RECENTLY_RECORDED -> {
|
||||
val title = ResStringProvider(R.string.recently_recorded)
|
||||
val title = getRecentlyAddedTitle(library.name)
|
||||
HomeRowConfigDisplay(
|
||||
id = id,
|
||||
title = title,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -1470,6 +1471,8 @@ class PlaybackViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
}.catch { ex ->
|
||||
Timber.e(ex, "Error in websocket subscription")
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -278,188 +278,182 @@ private fun SwitchServerContentInternal(
|
|||
}
|
||||
|
||||
if (showAddServer) {
|
||||
var showEnterAddress by remember { mutableStateOf(false) }
|
||||
AddServerDialog(
|
||||
onDismissRequest = { showAddServer = false },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.clearAddServerState()
|
||||
if (!showEnterAddress) {
|
||||
viewModel.discoverServers()
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
private fun AddServerDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: SwitchServerViewModel,
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
var showEnterAddress by remember { mutableStateOf(false) }
|
||||
|
||||
// Filter out duplicates within the discovered servers list (same URL appearing multiple times)
|
||||
val filteredDiscoveredServers =
|
||||
remember(state.discoveredServers) {
|
||||
val seenUrls = mutableSetOf<String>()
|
||||
state.discoveredServers.filter { server ->
|
||||
val normalizedUrl = server.url.lowercase().trim()
|
||||
if (normalizedUrl in seenUrls) {
|
||||
false // Duplicate, filter it out
|
||||
} else {
|
||||
seenUrls.add(normalizedUrl)
|
||||
true // First occurrence, keep it
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.clearAddServerState()
|
||||
if (!showEnterAddress) {
|
||||
viewModel.discoverServers()
|
||||
}
|
||||
}
|
||||
|
||||
val firstDiscoveredServerFocusRequester = remember { FocusRequester() }
|
||||
|
||||
// Default focus to first discovered server if available
|
||||
LaunchedEffect(state.discoveredServers.isNotEmpty(), showEnterAddress) {
|
||||
if (!showEnterAddress && state.discoveredServers.isNotEmpty()) {
|
||||
firstDiscoveredServerFocusRequester.tryRequestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
BasicDialog(
|
||||
onDismissRequest = {
|
||||
showEnterAddress = false
|
||||
viewModel.clearAddServerState()
|
||||
onDismissRequest.invoke()
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
elevation = 10.dp,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(.4f),
|
||||
) {
|
||||
if (!showEnterAddress) {
|
||||
// Show discovered servers first
|
||||
Text(
|
||||
text = stringResource(R.string.discovered_servers),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
||||
if (state.discoveredServers.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.searching),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 300.dp),
|
||||
) {
|
||||
items(
|
||||
state.discoveredServers.size,
|
||||
key = { state.discoveredServers[it].url },
|
||||
) { index ->
|
||||
val server = state.discoveredServers[index]
|
||||
val focusRequester =
|
||||
if (index == 0) {
|
||||
firstDiscoveredServerFocusRequester
|
||||
} else {
|
||||
remember { FocusRequester() }
|
||||
}
|
||||
|
||||
ListItem(
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = {
|
||||
Text(
|
||||
text =
|
||||
server.name?.ifBlank { null }
|
||||
?: server.url,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = server.url,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.addServer(server.url, showToast = true)
|
||||
},
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val firstDiscoveredServerFocusRequester = remember { FocusRequester() }
|
||||
|
||||
// Default focus to first discovered server if available
|
||||
LaunchedEffect(filteredDiscoveredServers.isNotEmpty(), showEnterAddress) {
|
||||
if (!showEnterAddress && filteredDiscoveredServers.isNotEmpty()) {
|
||||
firstDiscoveredServerFocusRequester.tryRequestFocus()
|
||||
TextButton(
|
||||
onClick = {
|
||||
showEnterAddress = true
|
||||
},
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
) {
|
||||
Text(text = stringResource(R.string.enter_server_address))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show enter server address form
|
||||
val addServerState = state.addServerState
|
||||
var url by remember { mutableStateOf("") }
|
||||
val submit = {
|
||||
viewModel.addServer(url, showToast = false)
|
||||
}
|
||||
val textBoxFocusRequester = remember { FocusRequester() }
|
||||
|
||||
BasicDialog(
|
||||
onDismissRequest = {
|
||||
showAddServer = false
|
||||
showEnterAddress = false
|
||||
LaunchedEffect(Unit) {
|
||||
textBoxFocusRequester.tryRequestFocus()
|
||||
}
|
||||
LaunchedEffect(url) {
|
||||
viewModel.clearAddServerState()
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
elevation = 10.dp,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.enter_server_url),
|
||||
)
|
||||
EditTextBox(
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Uri,
|
||||
imeAction = ImeAction.Go,
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onGo = { submit.invoke() },
|
||||
),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(.4f),
|
||||
.testTag("server_url_text")
|
||||
.focusRequester(textBoxFocusRequester)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
when (val st = addServerState) {
|
||||
is LoadingState.Error -> {
|
||||
Text(
|
||||
text =
|
||||
st.message ?: st.exception?.localizedMessage
|
||||
?: "An error occurred",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
TextButton(
|
||||
onClick = { submit.invoke() },
|
||||
enabled = url.isNotNullOrBlank() && addServerState == LoadingState.Pending,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
if (!showEnterAddress) {
|
||||
// Show discovered servers first
|
||||
Text(
|
||||
text = stringResource(R.string.discovered_servers),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
||||
if (filteredDiscoveredServers.isEmpty() && state.discoveredServers.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.searching),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
} else if (filteredDiscoveredServers.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_servers_found),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 300.dp),
|
||||
) {
|
||||
items(
|
||||
filteredDiscoveredServers.size,
|
||||
key = { filteredDiscoveredServers[it].url },
|
||||
) { index ->
|
||||
val server = filteredDiscoveredServers[index]
|
||||
val focusRequester =
|
||||
if (index == 0) {
|
||||
firstDiscoveredServerFocusRequester
|
||||
} else {
|
||||
remember { FocusRequester() }
|
||||
}
|
||||
|
||||
ListItem(
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = {
|
||||
Text(
|
||||
text =
|
||||
server.name?.ifBlank { null }
|
||||
?: server.url,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = server.url,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.addServer(server.url)
|
||||
},
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
showEnterAddress = true
|
||||
},
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
) {
|
||||
Text(text = stringResource(R.string.enter_server_address))
|
||||
}
|
||||
if (addServerState == LoadingState.Loading) {
|
||||
CircularProgress(Modifier.size(32.dp))
|
||||
} else {
|
||||
// Show enter server address form
|
||||
val addServerState = state.addServerState
|
||||
var url by remember { mutableStateOf("") }
|
||||
val submit = {
|
||||
viewModel.addServer(url)
|
||||
}
|
||||
val textBoxFocusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
textBoxFocusRequester.tryRequestFocus()
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.enter_server_url),
|
||||
)
|
||||
EditTextBox(
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Uri,
|
||||
imeAction = ImeAction.Go,
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onGo = { submit.invoke() },
|
||||
),
|
||||
modifier =
|
||||
Modifier
|
||||
.testTag("server_url_text")
|
||||
.focusRequester(textBoxFocusRequester)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
when (val st = addServerState) {
|
||||
is LoadingState.Error -> {
|
||||
Text(
|
||||
text =
|
||||
st.message ?: st.exception?.localizedMessage
|
||||
?: "An error occurred",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
TextButton(
|
||||
onClick = { submit.invoke() },
|
||||
enabled = url.isNotNullOrBlank() && addServerState == LoadingState.Pending,
|
||||
modifier = Modifier,
|
||||
) {
|
||||
if (addServerState == LoadingState.Loading) {
|
||||
CircularProgress(Modifier.size(32.dp))
|
||||
} else {
|
||||
Text(text = stringResource(R.string.submit))
|
||||
}
|
||||
}
|
||||
Text(text = stringResource(R.string.submit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.update
|
||||
import org.jellyfin.sdk.Jellyfin
|
||||
import org.jellyfin.sdk.api.client.HttpClientOptions
|
||||
import org.jellyfin.sdk.api.client.exception.TimeoutException
|
||||
import org.jellyfin.sdk.api.client.extensions.systemApi
|
||||
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
|
||||
import org.jellyfin.sdk.discovery.RecommendedServerIssue
|
||||
|
|
@ -134,6 +135,14 @@ class SwitchServerViewModel
|
|||
.getPublicSystemInfo()
|
||||
.content
|
||||
ServerConnectionStatus.Success(systemInfo)
|
||||
} catch (ex: TimeoutException) {
|
||||
Timber.w(ex, "timeout checking server ${server.url}")
|
||||
val cause = ex.cause
|
||||
if (cause is java.net.ConnectException) {
|
||||
ServerConnectionStatus.Error("${cause.localizedMessage}")
|
||||
} else {
|
||||
ServerConnectionStatus.Error(ex.localizedMessage)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error checking server ${server.url}")
|
||||
ServerConnectionStatus.Error(ex.localizedMessage)
|
||||
|
|
@ -155,12 +164,15 @@ class SwitchServerViewModel
|
|||
serverRepository.addAndChangeServer(updatedServer)
|
||||
navigationManager.navigateTo(SetupDestination.UserList(updatedServer))
|
||||
} else if (result is ServerConnectionStatus.Error) {
|
||||
showToast(context, "Error connecting: $${result.message}")
|
||||
showToast(context, "Error connecting: ${result.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addServer(inputUrl: String) {
|
||||
fun addServer(
|
||||
inputUrl: String,
|
||||
showToast: Boolean,
|
||||
) {
|
||||
_state.update { it.copy(addServerState = LoadingState.Loading) }
|
||||
viewModelScope.launchIO {
|
||||
try {
|
||||
|
|
@ -186,7 +198,9 @@ class SwitchServerViewModel
|
|||
_state.update { it.copy(addServerState = LoadingState.Success) }
|
||||
navigationManager.navigateTo(SetupDestination.UserList(server))
|
||||
} else {
|
||||
_state.update { it.copy(addServerState = LoadingState.Error("Server returned invalid response")) }
|
||||
val msg = "Server returned invalid response"
|
||||
_state.update { it.copy(addServerState = LoadingState.Error(msg)) }
|
||||
if (showToast) showToast(context, msg)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Error connecting with %s: %s", inputUrl, scores)
|
||||
|
|
@ -215,10 +229,12 @@ class SwitchServerViewModel
|
|||
}
|
||||
val message = "Error, tried addresses:\n$errors"
|
||||
_state.update { it.copy(addServerState = LoadingState.Error(message)) }
|
||||
if (showToast) showToast(context, message)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error creating API for $inputUrl")
|
||||
_state.update { it.copy(addServerState = LoadingState.Error(exception = ex)) }
|
||||
if (showToast) showToast(context, "Error: ${ex.localizedMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -247,6 +263,9 @@ class SwitchServerViewModel
|
|||
.toMutableList()
|
||||
.apply {
|
||||
add(jellyfinServer)
|
||||
}.distinctBy {
|
||||
// Filter out duplicates within the discovered servers list (same URL appearing multiple times)
|
||||
it.url.lowercase().trim()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue