mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +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.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
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.ArrowDropDown
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
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.spacedByWithFooter
|
||||||
import com.github.damontecres.wholphin.ui.theme.LocalTheme
|
import com.github.damontecres.wholphin.ui.theme.LocalTheme
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
|
|
@ -99,6 +99,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
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 org.jellyfin.sdk.model.api.CollectionType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -108,6 +110,7 @@ import javax.inject.Inject
|
||||||
class NavDrawerViewModel
|
class NavDrawerViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
|
private val api: ApiClient,
|
||||||
private val navDrawerItemRepository: NavDrawerItemRepository,
|
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
val setupNavigationManager: SetupNavigationManager,
|
val setupNavigationManager: SetupNavigationManager,
|
||||||
|
|
@ -191,6 +194,8 @@ class NavDrawerViewModel
|
||||||
fun setShowMore(value: Boolean) {
|
fun setShowMore(value: Boolean) {
|
||||||
showMore.value = value
|
showMore.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUserImage(user: JellyfinUser): String = api.imageApi.getUserImageUrl(user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface NavDrawerItem {
|
sealed interface NavDrawerItem {
|
||||||
|
|
@ -386,12 +391,11 @@ fun NavDrawer(
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
||||||
IconNavItem(
|
val userImageUrl = remember(user) { viewModel.getUserImage(user) }
|
||||||
text = user?.name ?: "",
|
ProfileIcon(
|
||||||
subtext = server?.name ?: server?.url,
|
user = user,
|
||||||
icon = Icons.Default.AccountCircle,
|
imageUrl = userImageUrl,
|
||||||
selected = false,
|
serverName = server.name ?: server.url,
|
||||||
drawerOpen = drawerState.isOpen,
|
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setupNavigationManager.navigateTo(
|
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
|
@Composable
|
||||||
fun NavigationDrawerScope.IconNavItem(
|
fun NavigationDrawerScope.IconNavItem(
|
||||||
text: String,
|
text: String,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.wholphin.ui.setup
|
package com.github.damontecres.wholphin.ui.setup
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.tv.material3.Border
|
import androidx.tv.material3.Border
|
||||||
import androidx.tv.material3.Button
|
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.data.model.JellyfinUser
|
||||||
import com.github.damontecres.wholphin.ui.Cards
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
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.DialogItem
|
||||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
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
|
* 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
|
* Add User card component - displays a + icon in a circle
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue