mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Use the user's profile image on nav drawer (#723)
## Description Replaces the generic user icon with the user's profile image on the nav drawer If there's no image it will fall back to the generated color & first letter just like on the user list page. ### Related issues Closes #631 ### Screenshots <img width="54" height="160" alt="image" src="https://github.com/user-attachments/assets/bee21e56-104a-420f-bb7d-aae717f8aeb0" />
This commit is contained in:
parent
4dd44b935c
commit
946043342a
2 changed files with 112 additions and 7 deletions
|
|
@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
|
|
@ -87,6 +86,7 @@ import com.github.damontecres.wholphin.ui.ifElse
|
|||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.setup.UserIconCardImage
|
||||
import com.github.damontecres.wholphin.ui.spacedByWithFooter
|
||||
import com.github.damontecres.wholphin.ui.theme.LocalTheme
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
|
|
@ -99,6 +99,8 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
|
@ -108,6 +110,7 @@ import javax.inject.Inject
|
|||
class NavDrawerViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||
val navigationManager: NavigationManager,
|
||||
val setupNavigationManager: SetupNavigationManager,
|
||||
|
|
@ -191,6 +194,8 @@ class NavDrawerViewModel
|
|||
fun setShowMore(value: Boolean) {
|
||||
showMore.value = value
|
||||
}
|
||||
|
||||
fun getUserImage(user: JellyfinUser): String = api.imageApi.getUserImageUrl(user.id)
|
||||
}
|
||||
|
||||
sealed interface NavDrawerItem {
|
||||
|
|
@ -386,12 +391,11 @@ fun NavDrawer(
|
|||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
||||
IconNavItem(
|
||||
text = user?.name ?: "",
|
||||
subtext = server?.name ?: server?.url,
|
||||
icon = Icons.Default.AccountCircle,
|
||||
selected = false,
|
||||
drawerOpen = drawerState.isOpen,
|
||||
val userImageUrl = remember(user) { viewModel.getUserImage(user) }
|
||||
ProfileIcon(
|
||||
user = user,
|
||||
imageUrl = userImageUrl,
|
||||
serverName = server.name ?: server.url,
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.setupNavigationManager.navigateTo(
|
||||
|
|
@ -572,6 +576,44 @@ fun NavDrawer(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationDrawerScope.ProfileIcon(
|
||||
user: JellyfinUser,
|
||||
imageUrl: String?,
|
||||
serverName: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
leadingContent = {
|
||||
UserIconCardImage(
|
||||
id = user.id,
|
||||
name = user.name,
|
||||
imageUrl = imageUrl,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = serverName,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = user.name ?: user.id.toString(),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationDrawerScope.IconNavItem(
|
||||
text: String,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.wholphin.ui.setup
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -38,6 +39,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.tv.material3.Border
|
||||
import androidx.tv.material3.Button
|
||||
|
|
@ -51,10 +53,13 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Display a list of users plus option to add a new one or switch servers
|
||||
|
|
@ -285,6 +290,64 @@ private fun UserIconCard(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserIconCardImage(
|
||||
id: UUID,
|
||||
name: String?,
|
||||
imageUrl: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var imageError by remember { mutableStateOf(false) }
|
||||
val userColor = rememberIdColor(id)
|
||||
Box(
|
||||
modifier =
|
||||
modifier.background(
|
||||
color = userColor,
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (imageUrl.isNotNullOrBlank() && !imageError) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = name,
|
||||
contentScale = ContentScale.Crop,
|
||||
onError = { imageError = true },
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.clip(CircleShape),
|
||||
)
|
||||
} else {
|
||||
val firstLetter =
|
||||
remember(id, name) {
|
||||
(name ?: id.toString()).firstOrNull()?.uppercase() ?: "?"
|
||||
}
|
||||
Text(
|
||||
text = firstLetter,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
fun UserIconCardImagePreview() {
|
||||
WholphinTheme {
|
||||
UserIconCardImage(
|
||||
id = UUID.randomUUID(),
|
||||
name = "A smith",
|
||||
imageUrl = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add User card component - displays a + icon in a circle
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue