mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Few minor UI fixes (#343)
- Keep the currently selected nav drawer item in view and focus on when entering - Adjust star rating height so that the row is consistent preventing resizing when moving between episodes with & without a rating - Simplify checks when switching to a new server and use more specific keys to prevent needing to switch servers twice
This commit is contained in:
parent
13a3c6c831
commit
ff91fa87e2
7 changed files with 125 additions and 39 deletions
|
|
@ -110,15 +110,15 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
isRestoringSession = false
|
||||
}
|
||||
val server by serverRepository.currentServer.observeAsState()
|
||||
val user by serverRepository.currentUser.observeAsState()
|
||||
val userDto by serverRepository.currentUserDto.observeAsState()
|
||||
val current by serverRepository.current.observeAsState()
|
||||
|
||||
val preferences =
|
||||
UserPreferences(
|
||||
appPreferences,
|
||||
userDto?.configuration ?: DefaultUserConfiguration,
|
||||
)
|
||||
remember(current) {
|
||||
UserPreferences(
|
||||
appPreferences,
|
||||
current?.userDto?.configuration ?: DefaultUserConfiguration,
|
||||
)
|
||||
}
|
||||
|
||||
if (isRestoringSession) {
|
||||
Box(
|
||||
|
|
@ -131,9 +131,9 @@ class MainActivity : AppCompatActivity() {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
key(server, user) {
|
||||
key(current) {
|
||||
val initialDestination =
|
||||
if (server != null && user != null) {
|
||||
if (current != null) {
|
||||
Destination.Home()
|
||||
} else {
|
||||
Destination.ServerList
|
||||
|
|
@ -150,16 +150,16 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
val deviceProfile =
|
||||
remember(server, appPreferences) {
|
||||
remember(current, appPreferences) {
|
||||
createDeviceProfile(
|
||||
this@MainActivity,
|
||||
preferences,
|
||||
server?.serverVersion,
|
||||
current?.server?.serverVersion,
|
||||
)
|
||||
}
|
||||
ApplicationContent(
|
||||
user = user,
|
||||
server = server,
|
||||
user = current?.user,
|
||||
server = current?.server,
|
||||
navigationManager = navigationManager,
|
||||
preferences = preferences,
|
||||
deviceProfile = deviceProfile,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.github.damontecres.wholphin.ui.components
|
|||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -78,10 +79,19 @@ fun DotSeparatedRow(
|
|||
@Composable
|
||||
private fun DotSeparatedRowPreview() {
|
||||
WholphinTheme {
|
||||
DotSeparatedRow(
|
||||
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left"),
|
||||
rating = 7.5f,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Column {
|
||||
DotSeparatedRow(
|
||||
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left"),
|
||||
rating = 7.5f,
|
||||
modifier = Modifier,
|
||||
textStyle = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
DotSeparatedRow(
|
||||
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left 7.5"),
|
||||
rating = 7.5f,
|
||||
modifier = Modifier,
|
||||
textStyle = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import android.R.attr.textStyle
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -35,10 +36,12 @@ import androidx.compose.ui.geometry.Offset
|
|||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.LocalContentColor
|
||||
import androidx.tv.material3.LocalTextStyle
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
|
|
@ -100,11 +103,12 @@ fun SimpleStarRating(
|
|||
text = text,
|
||||
modifier = Modifier,
|
||||
)
|
||||
val height = with(LocalDensity.current) { LocalTextStyle.current.fontSize.toDp() }
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Star,
|
||||
tint = starColor,
|
||||
contentDescription = null,
|
||||
modifier = Modifier,
|
||||
modifier = Modifier.height(height),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,9 +41,12 @@ import androidx.compose.ui.focus.FocusDirection
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -85,12 +88,15 @@ import com.github.damontecres.wholphin.ui.spacedByWithFooter
|
|||
import com.github.damontecres.wholphin.ui.theme.LocalTheme
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -303,6 +309,28 @@ fun NavDrawer(
|
|||
},
|
||||
)
|
||||
val spacedBy = 4.dp
|
||||
val config = LocalConfiguration.current
|
||||
val density = LocalDensity.current
|
||||
val heightInPx = remember { with(density) { config.screenHeightDp.dp.roundToPx() } }
|
||||
|
||||
suspend fun scrollToSelected() {
|
||||
val target = selectedIndex + 2
|
||||
try {
|
||||
if (target !in
|
||||
listState.firstVisibleItemIndex..<listState.layoutInfo.visibleItemsInfo.lastIndex
|
||||
) {
|
||||
val mult = if ((target - 2) < listState.layoutInfo.totalItemsCount / 2) -1 else 1
|
||||
listState.animateScrollToItem(selectedIndex + 2, mult * (heightInPx / 2))
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error scrolling to %s", target)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(selectedIndex) {
|
||||
scrollToSelected()
|
||||
}
|
||||
|
||||
NavigationDrawer(
|
||||
modifier = modifier,
|
||||
drawerState = drawerState,
|
||||
|
|
@ -315,7 +343,11 @@ fun NavDrawer(
|
|||
Modifier
|
||||
.fillMaxHeight()
|
||||
.width(drawerWidth)
|
||||
.background(drawerBackground),
|
||||
.background(drawerBackground)
|
||||
.onFocusChanged {
|
||||
if (!it.hasFocus) {
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Even though some must be clicked, focusing on it should clear other focused items
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
|
@ -353,6 +385,11 @@ fun NavDrawer(
|
|||
focusRequester.tryRequestFocus()
|
||||
}
|
||||
}
|
||||
onExit = {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
scrollToSelected()
|
||||
}
|
||||
}
|
||||
}.fillMaxHeight()
|
||||
.padding(start = drawerPadding),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -29,10 +29,13 @@ import com.github.damontecres.wholphin.ui.components.CircularProgress
|
|||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import org.jellyfin.sdk.model.api.PublicSystemInfo
|
||||
import java.util.UUID
|
||||
|
||||
sealed interface ServerConnectionStatus {
|
||||
object Success : ServerConnectionStatus
|
||||
data class Success(
|
||||
val systemInfo: PublicSystemInfo,
|
||||
) : ServerConnectionStatus
|
||||
|
||||
object Pending : ServerConnectionStatus
|
||||
|
||||
|
|
@ -68,7 +71,7 @@ fun ServerList(
|
|||
supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) },
|
||||
leadingContent = {
|
||||
when (status) {
|
||||
ServerConnectionStatus.Success -> {}
|
||||
is ServerConnectionStatus.Success -> {}
|
||||
ServerConnectionStatus.Pending -> {
|
||||
CircularProgress(
|
||||
Modifier.size(IconButtonDefaults.MediumIconSize),
|
||||
|
|
@ -85,7 +88,7 @@ fun ServerList(
|
|||
},
|
||||
onClick = {
|
||||
when (status) {
|
||||
ServerConnectionStatus.Success -> onSwitchServer.invoke(server)
|
||||
is ServerConnectionStatus.Success -> onSwitchServer.invoke(server)
|
||||
ServerConnectionStatus.Pending -> {}
|
||||
is ServerConnectionStatus.Error -> onTestServer.invoke(server)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import com.github.damontecres.wholphin.ui.components.EditTextBox
|
|||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import org.jellyfin.sdk.model.api.PublicSystemInfo
|
||||
|
||||
@Composable
|
||||
fun SwitchServerContent(
|
||||
|
|
@ -88,7 +89,7 @@ fun SwitchServerContent(
|
|||
servers = servers,
|
||||
connectionStatus = serverStatus,
|
||||
onSwitchServer = {
|
||||
viewModel.addServer(it.url)
|
||||
viewModel.switchServer(it)
|
||||
},
|
||||
onTestServer = {
|
||||
viewModel.testServer(it)
|
||||
|
|
@ -141,7 +142,7 @@ fun SwitchServerContent(
|
|||
connectionStatus =
|
||||
discoveredServers
|
||||
.map { it.id }
|
||||
.associateWith { ServerConnectionStatus.Success },
|
||||
.associateWith { ServerConnectionStatus.Success(PublicSystemInfo()) },
|
||||
onSwitchServer = {
|
||||
viewModel.addServer(it.url)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class SwitchServerViewModel
|
|||
viewModelScope.launchIO {
|
||||
delay(1000)
|
||||
val result = internalTestServer(server)
|
||||
if (result == ServerConnectionStatus.Success) {
|
||||
if (result is ServerConnectionStatus.Success) {
|
||||
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||
} else if (result is ServerConnectionStatus.Error) {
|
||||
showToast(context, result.message ?: "Error", Toast.LENGTH_SHORT)
|
||||
|
|
@ -97,27 +97,30 @@ class SwitchServerViewModel
|
|||
|
||||
private suspend fun internalTestServer(server: JellyfinServer): ServerConnectionStatus =
|
||||
try {
|
||||
jellyfin
|
||||
.createApi(
|
||||
server.url,
|
||||
httpClientOptions =
|
||||
HttpClientOptions(
|
||||
requestTimeout = 6.seconds,
|
||||
connectTimeout = 6.seconds,
|
||||
socketTimeout = 6.seconds,
|
||||
),
|
||||
).systemApi
|
||||
.getPublicSystemInfo()
|
||||
val systemInfo =
|
||||
jellyfin
|
||||
.createApi(
|
||||
server.url,
|
||||
httpClientOptions =
|
||||
HttpClientOptions(
|
||||
requestTimeout = 6.seconds,
|
||||
connectTimeout = 6.seconds,
|
||||
socketTimeout = 6.seconds,
|
||||
),
|
||||
).systemApi
|
||||
.getPublicSystemInfo()
|
||||
.content
|
||||
val result = ServerConnectionStatus.Success(systemInfo)
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Success,
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
ServerConnectionStatus.Success
|
||||
result
|
||||
} catch (ex: Exception) {
|
||||
val status = ServerConnectionStatus.Error(ex.localizedMessage)
|
||||
Timber.w(ex, "Error checking server ${server.url}")
|
||||
|
|
@ -133,6 +136,34 @@ class SwitchServerViewModel
|
|||
status
|
||||
}
|
||||
|
||||
fun switchServer(server: JellyfinServer) {
|
||||
viewModelScope.launchIO {
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Pending,
|
||||
)
|
||||
}
|
||||
}
|
||||
val result = internalTestServer(server)
|
||||
if (result is ServerConnectionStatus.Success) {
|
||||
val updatedServer =
|
||||
server.copy(
|
||||
name = result.systemInfo.serverName,
|
||||
version = result.systemInfo.version,
|
||||
)
|
||||
serverRepository.addAndChangeServer(updatedServer)
|
||||
withContext(Dispatchers.Main) {
|
||||
navigationManager.navigateTo(Destination.UserList(server))
|
||||
}
|
||||
} else if (result is ServerConnectionStatus.Error) {
|
||||
showToast(context, "Error connecting: $${result.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addServer(inputUrl: String) {
|
||||
addServerState.value = LoadingState.Loading
|
||||
viewModelScope.launchIO {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue