From ffc704bdce90de90f7865debcaa55a1cee08fa90 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Mon, 6 Oct 2025 23:58:39 -0400 Subject: [PATCH] Handle remove users/servers --- .../damontecres/dolphin/MainActivity.kt | 2 +- .../dolphin/data/ServerRepository.kt | 34 +++++ .../dolphin/ui/components/Dialogs.kt | 10 +- .../dolphin/ui/setup/ServerList.kt | 51 ++++++- .../dolphin/ui/setup/SwitchServerContent.kt | 141 +++++++++++------- .../dolphin/ui/setup/SwitchUserContent.kt | 77 ++++++---- .../dolphin/ui/setup/SwitchUserViewModel.kt | 12 ++ .../damontecres/dolphin/ui/setup/UserList.kt | 60 +++++++- app/src/main/res/values/fa_strings.xml | 1 + 9 files changed, 294 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt index 4c533c2b..eb3a415d 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt @@ -72,7 +72,7 @@ class MainActivity : AppCompatActivity() { val appPreferences by userPreferencesDataStore.data.collectAsState(null) appPreferences?.let { appPreferences -> LaunchedEffect(Unit) { - if (appPreferences.currentServerId.isNotBlank() && appPreferences.currentUserId.isNotBlank()) { + if (appPreferences.currentServerId.isNotBlank()) { serverRepository.restoreSession( appPreferences.currentServerId, appPreferences.currentUserId, diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt index b023d70e..6f77521f 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt @@ -102,6 +102,7 @@ class ServerRepository serverDao.getServer(serverId) } if (serverAndUsers != null) { + _currentServer = serverAndUsers.server val user = serverAndUsers.users.firstOrNull { it.id == userId } if (user != null) { changeUser(serverAndUsers.server, user) @@ -142,4 +143,37 @@ class ServerRepository } } } + + suspend fun removeUser(user: JellyfinUser) { + if (currentUser == user) { + _currentUser = null + userPreferencesDataStore.updateData { + it + .toBuilder() + .apply { + currentUserId = "" + }.build() + } + apiClient.update(accessToken = null) + } + withContext(Dispatchers.IO) { + serverDao.deleteUser(user.serverId, user.id) + } + } + + suspend fun removeServer(server: JellyfinServer) { + if (currentServer == server) { + _currentServer = null + userPreferencesDataStore.updateData { + it + .toBuilder() + .apply { + currentServerId = "" + }.build() + } + } + withContext(Dispatchers.IO) { + serverDao.deleteServer(server.id) + } + } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/components/Dialogs.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/components/Dialogs.kt index a1549e6e..10f2f76b 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/components/Dialogs.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/components/Dialogs.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.key.Key @@ -37,11 +38,13 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.tv.material3.Icon import androidx.tv.material3.ListItem +import androidx.tv.material3.LocalContentColor import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import androidx.tv.material3.surfaceColorAtElevation @@ -93,6 +96,7 @@ data class DialogItem( constructor( text: String, icon: ImageVector, + iconColor: Color? = null, onClick: () -> Unit, ) : this( headlineContent = { @@ -105,7 +109,7 @@ data class DialogItem( Icon( imageVector = icon, contentDescription = text, -// tint = MaterialTheme.colorScheme.onPrimaryContainer, + tint = iconColor ?: LocalContentColor.current, ) }, onClick = onClick, @@ -139,6 +143,7 @@ fun DialogPopup( dismissOnClick: Boolean = true, waitToLoad: Boolean = true, properties: DialogProperties = DialogProperties(), + elevation: Dp = 3.dp, ) { var waiting by remember { mutableStateOf(waitToLoad) } if (showDialog) { @@ -158,7 +163,8 @@ fun DialogPopup( onDismissRequest = onDismissRequest, properties = properties, ) { - val elevatedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp) + val elevatedContainerColor = + MaterialTheme.colorScheme.surfaceColorAtElevation(elevation) LazyColumn( modifier = Modifier diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/ServerList.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/ServerList.kt index 09cd04c2..cdc39f52 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/ServerList.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/ServerList.kt @@ -3,16 +3,29 @@ package com.github.damontecres.dolphin.ui.setup import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties import androidx.tv.material3.Icon import androidx.tv.material3.ListItem import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text +import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.data.JellyfinServer import com.github.damontecres.dolphin.ui.components.CircularProgress +import com.github.damontecres.dolphin.ui.components.DialogItem +import com.github.damontecres.dolphin.ui.components.DialogPopup +import com.github.damontecres.dolphin.ui.isNotNullOrBlank sealed interface ServerConnectionStatus { object Success : ServerConnectionStatus @@ -33,14 +46,16 @@ fun ServerList( onRemoveServer: (JellyfinServer) -> Unit, modifier: Modifier = Modifier, ) { + var showDeleteDialog by remember { mutableStateOf(null) } + LazyColumn(modifier = modifier) { items(servers) { server -> val status = connectionStatus[server.id] ?: ServerConnectionStatus.Pending ListItem( enabled = status == ServerConnectionStatus.Success, selected = false, - headlineContent = { Text(text = server.name ?: server.id) }, - supportingContent = { Text(text = server.url) }, + headlineContent = { Text(text = server.name?.ifBlank { null } ?: server.url) }, + supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) }, leadingContent = { when (status) { ServerConnectionStatus.Success -> {} @@ -69,10 +84,40 @@ fun ServerList( enabled = true, selected = false, headlineContent = { Text(text = "Add Server") }, - supportingContent = { }, + leadingContent = { + Icon( + imageVector = Icons.Default.Add, + tint = Color.Green.copy(alpha = .8f), + contentDescription = null, + ) + }, onClick = { onAddServer.invoke() }, modifier = Modifier, ) } } + showDeleteDialog?.let { server -> + DialogPopup( + showDialog = true, + title = server.name ?: server.url, + dialogItems = + listOf( + DialogItem("Switch", R.string.fa_arrow_left_arrow_right) { + onSwitchServer.invoke(server) + }, + DialogItem( + "Delete", + Icons.Default.Delete, + Color.Red.copy(alpha = .8f), + ) { + onRemoveServer.invoke(server) + }, + ), + onDismissRequest = { showDeleteDialog = null }, + dismissOnClick = true, + waitToLoad = true, + properties = DialogProperties(), + elevation = 5.dp, + ) + } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchServerContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchServerContent.kt index c06156bb..74f39461 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchServerContent.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchServerContent.kt @@ -1,9 +1,12 @@ package com.github.damontecres.dolphin.ui.setup +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable @@ -19,7 +22,9 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.tv.material3.Button +import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text +import androidx.tv.material3.surfaceColorAtElevation import com.github.damontecres.dolphin.ui.components.BasicDialog import com.github.damontecres.dolphin.ui.components.EditTextBox import com.github.damontecres.dolphin.ui.nav.Destination @@ -37,71 +42,91 @@ fun SwitchServerContent( var showAddServer by remember { mutableStateOf(false) } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), + Box( modifier = modifier, ) { - Text( - text = "Select Server", - ) - ServerList( - servers = servers, - connectionStatus = serverStatus, - onSwitchServer = { - viewModel.addServer(it.url) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .fillMaxWidth(.5f) + .align(Alignment.Center) + .padding(16.dp) + .background( + MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp), + shape = RoundedCornerShape(16.dp), + ), + ) { + Text( + text = "Select Server", + style = MaterialTheme.typography.displaySmall, + color = MaterialTheme.colorScheme.onSurface, + ) + ServerList( + servers = servers, + connectionStatus = serverStatus, + onSwitchServer = { + viewModel.addServer(it.url) { + navigationManager.navigateTo(Destination.UserList) + } + }, + onAddServer = { + showAddServer = true + }, + onRemoveServer = { + viewModel.removeServer(it) + }, + modifier = + Modifier + .fillMaxWidth() + .background( + MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), + shape = RoundedCornerShape(16.dp), + ), + ) + } + + if (showAddServer) { + var url by remember { mutableStateOf("") } + val submit = { + showAddServer = false + viewModel.addServer(url) { navigationManager.navigateTo(Destination.UserList) } - }, - onAddServer = { - showAddServer = true - }, - onRemoveServer = { - // TODO - }, - modifier = Modifier.fillMaxWidth(.5f), - ) - } - - if (showAddServer) { - var url by remember { mutableStateOf("") } - val submit = { - showAddServer = false - viewModel.addServer(url) { - navigationManager.navigateTo(Destination.UserList) } - } - BasicDialog( - onDismissRequest = { showAddServer = false }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(16.dp), + BasicDialog( + onDismissRequest = { showAddServer = false }, ) { - Text( - text = "Enter Server URL", - ) - EditTextBox( - value = url, - onValueChange = { url = it }, - keyboardOptions = - KeyboardOptions( - capitalization = KeyboardCapitalization.None, - autoCorrectEnabled = false, - keyboardType = KeyboardType.Uri, - ), - keyboardActions = - KeyboardActions( - onDone = { submit.invoke() }, - ), - modifier = Modifier, - ) - Button( - onClick = { submit.invoke() }, - modifier = Modifier, + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp), ) { - Text(text = "Submit") + Text( + text = "Enter Server URL", + ) + EditTextBox( + value = url, + onValueChange = { url = it }, + keyboardOptions = + KeyboardOptions( + capitalization = KeyboardCapitalization.None, + autoCorrectEnabled = false, + keyboardType = KeyboardType.Uri, + ), + keyboardActions = + KeyboardActions( + onDone = { submit.invoke() }, + ), + modifier = Modifier, + ) + Button( + onClick = { submit.invoke() }, + modifier = Modifier, + ) { + Text(text = "Submit") + } } } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserContent.kt index 0e560872..47b6eb4d 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserContent.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserContent.kt @@ -1,10 +1,13 @@ package com.github.damontecres.dolphin.ui.setup +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable @@ -23,6 +26,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.tv.material3.Button import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text +import androidx.tv.material3.surfaceColorAtElevation import com.github.damontecres.dolphin.ui.components.BasicDialog import com.github.damontecres.dolphin.ui.components.EditTextBox import com.github.damontecres.dolphin.ui.isNotNullOrBlank @@ -45,33 +49,56 @@ fun SwitchUserContent( var showAddUser by remember { mutableStateOf(false) } currentServer?.let { server -> - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), + Box( modifier = modifier, ) { - Text( - text = server.name ?: server.url, - ) - Text( - text = "Select User", - ) - UserList( - users = users, - currentUser = currentUser, - onSwitchUser = { user -> - viewModel.switchUser(server, user) { - navigationManager.goToHome() - } - }, - onAddUser = { showAddUser = true }, - onRemoveUser = { user -> - }, - onSwitchServer = { - navigationManager.navigateTo(Destination.ServerList) - }, - modifier = Modifier.fillMaxWidth(.5f), - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .fillMaxWidth(.5f) + .align(Alignment.Center) + .padding(16.dp) + .background( + MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp), + shape = RoundedCornerShape(16.dp), + ), + ) { + Text( + text = "Select User", + style = MaterialTheme.typography.displaySmall, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = server.name ?: server.url, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + UserList( + users = users, + currentUser = currentUser, + onSwitchUser = { user -> + viewModel.switchUser(server, user) { + navigationManager.goToHome() + } + }, + onAddUser = { showAddUser = true }, + onRemoveUser = { user -> + viewModel.removeUser(user) + }, + onSwitchServer = { + navigationManager.navigateTo(Destination.ServerList) + }, + modifier = + Modifier + .fillMaxWidth() + .background( + MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), + shape = RoundedCornerShape(16.dp), + ), + ) + } } if (showAddUser) { diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserViewModel.kt index 8556a8eb..29fe9e83 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/SwitchUserViewModel.kt @@ -241,4 +241,16 @@ class SwitchUserViewModel } } } + + fun removeUser(user: JellyfinUser) { + viewModelScope.launch(ExceptionHandler()) { + serverRepository.removeUser(user) + } + } + + fun removeServer(server: JellyfinServer) { + viewModelScope.launch(ExceptionHandler()) { + serverRepository.removeServer(server) + } + } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/UserList.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/UserList.kt index eaa09d41..87bf3822 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/setup/UserList.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/setup/UserList.kt @@ -3,14 +3,28 @@ package com.github.damontecres.dolphin.ui.setup import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties import androidx.tv.material3.Icon import androidx.tv.material3.ListItem import androidx.tv.material3.Text +import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.data.JellyfinUser +import com.github.damontecres.dolphin.ui.FontAwesome +import com.github.damontecres.dolphin.ui.components.DialogItem +import com.github.damontecres.dolphin.ui.components.DialogPopup @Composable fun UserList( @@ -22,13 +36,14 @@ fun UserList( onSwitchServer: () -> Unit, modifier: Modifier = Modifier, ) { + var showDeleteDialog by remember { mutableStateOf(null) } + LazyColumn(modifier = modifier) { items(users) { user -> ListItem( enabled = true, - selected = false, + selected = user == currentUser, headlineContent = { Text(text = user.name ?: user.id) }, - supportingContent = { }, leadingContent = { if (user.id == currentUser?.id) { Icon( @@ -39,7 +54,7 @@ fun UserList( }, onClick = { onSwitchUser.invoke(user) }, onLongClick = { - // TODO dialog to remove user + showDeleteDialog = user }, modifier = Modifier, ) @@ -50,7 +65,13 @@ fun UserList( enabled = true, selected = false, headlineContent = { Text(text = "Add User") }, - supportingContent = { }, + leadingContent = { + Icon( + imageVector = Icons.Default.Add, + tint = Color.Green.copy(alpha = .8f), + contentDescription = null, + ) + }, onClick = { onAddUser.invoke() }, modifier = Modifier, ) @@ -61,10 +82,39 @@ fun UserList( enabled = true, selected = false, headlineContent = { Text(text = "Switch servers") }, - supportingContent = { }, + leadingContent = { + Text( + text = stringResource(R.string.fa_arrow_left_arrow_right), + fontFamily = FontAwesome, + ) + }, onClick = { onSwitchServer.invoke() }, modifier = Modifier, ) } } + showDeleteDialog?.let { user -> + DialogPopup( + showDialog = true, + title = user.name ?: user.id, + dialogItems = + listOf( + DialogItem("Switch", R.string.fa_arrow_left_arrow_right) { + onSwitchUser.invoke(user) + }, + DialogItem( + "Delete", + Icons.Default.Delete, + Color.Red.copy(alpha = .8f), + ) { + onRemoveUser.invoke(user) + }, + ), + onDismissRequest = { showDeleteDialog = null }, + dismissOnClick = true, + waitToLoad = true, + properties = DialogProperties(), + elevation = 5.dp, + ) + } } diff --git a/app/src/main/res/values/fa_strings.xml b/app/src/main/res/values/fa_strings.xml index 4a170f54..161d8ff6 100644 --- a/app/src/main/res/values/fa_strings.xml +++ b/app/src/main/res/values/fa_strings.xml @@ -30,4 +30,5 @@ +