diff --git a/src/main/kotlin/core/models/SelectedCredential.kt b/src/main/kotlin/core/models/SelectedCredential.kt index a5fbefe..bb844bd 100644 --- a/src/main/kotlin/core/models/SelectedCredential.kt +++ b/src/main/kotlin/core/models/SelectedCredential.kt @@ -41,4 +41,12 @@ data class SelectedCredential( return password != null || creditCard != null } + fun isFavorite(): Boolean { + return (password != null || creditCard != null) && (password?.favorite == true || creditCard?.favorite == true) + } + + fun getId(): UUID? { + return password?.id?.value ?: creditCard?.id?.value + } + } \ No newline at end of file diff --git a/src/main/kotlin/core/models/UiModel.kt b/src/main/kotlin/core/models/UiModel.kt index ae0366e..74f5e6e 100644 --- a/src/main/kotlin/core/models/UiModel.kt +++ b/src/main/kotlin/core/models/UiModel.kt @@ -13,7 +13,8 @@ data class CredentialDisplay( val id: UUID, val title: String, val description: String, - val favorite: Boolean + val favorite: Boolean, + val isSelected: Boolean ) enum class CredentialSort(val value: String) { diff --git a/src/main/kotlin/repository/creditcard/CreditCardExposedEntity.kt b/src/main/kotlin/repository/creditcard/CreditCardExposedEntity.kt index 1a17669..2ebeaf4 100644 --- a/src/main/kotlin/repository/creditcard/CreditCardExposedEntity.kt +++ b/src/main/kotlin/repository/creditcard/CreditCardExposedEntity.kt @@ -40,4 +40,11 @@ class CreditCard(id: EntityID) : UUIDEntity(id) { var lastUpdateDateTime by CreditCardTable.lastUpdateDateTime var lastUpdatedBy by CreditCardTable.lastUpdatedBy var version by CreditCardTable.version + + fun favorite(): CreditCard { + return this.apply { + favorite = true + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/repository/creditcard/CreditCardRepository.kt b/src/main/kotlin/repository/creditcard/CreditCardRepository.kt index d14e3be..2e01dc9 100644 --- a/src/main/kotlin/repository/creditcard/CreditCardRepository.kt +++ b/src/main/kotlin/repository/creditcard/CreditCardRepository.kt @@ -16,4 +16,6 @@ interface CreditCardRepository { suspend fun update(id: UUID, user: String, creditCardDto: CreditCardDto): Result + suspend fun favorite(id: UUID, user: String): Result + } \ No newline at end of file diff --git a/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt b/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt index a4221bc..e85fce6 100644 --- a/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt +++ b/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt @@ -117,6 +117,24 @@ class CreditCardRepositoryImpl( } } + override suspend fun favorite(id: UUID, user: String): Result { + return try { + return transaction(db) { + CreditCard.findById(id)?.let { + it.favorite = !it.favorite + it.lastUpdateDateTime = LocalDateTime.now() + it.lastUpdatedBy = user + it.version += 1 + } + }.let { + Result.Success(true) + } + } catch (e: Exception) { + logger.error(e.message, e) + Result.Error(DatabaseError.fromException(e).extractMessage()) + } + } + private fun toSort(sort: CredentialSort): Expression<*> { return when (sort) { CredentialSort.NAME -> CreditCardTable.name diff --git a/src/main/kotlin/repository/password/PasswordRepository.kt b/src/main/kotlin/repository/password/PasswordRepository.kt index aa7ce7e..ea7dda6 100644 --- a/src/main/kotlin/repository/password/PasswordRepository.kt +++ b/src/main/kotlin/repository/password/PasswordRepository.kt @@ -16,4 +16,6 @@ interface PasswordRepository { suspend fun update(id: UUID, user: String, password: PasswordDto): Result + suspend fun favorite(id: UUID, user: String): Result + } \ No newline at end of file diff --git a/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt b/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt index 9cf3e3c..6e3ebdf 100644 --- a/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt +++ b/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt @@ -120,6 +120,24 @@ class PasswordRepositoryImpl( } } + override suspend fun favorite(id: UUID, user: String): Result { + return try { + return transaction(db) { + Password.findById(id)?.let { + it.favorite = !it.favorite + it.lastUpdatedBy = user + it.lastUpdateDateTime = LocalDateTime.now() + it.version += 1 + } + }.let { + Result.Success(true) + } + } catch (e: Exception) { + logger.error(e.message, e) + Result.Error(DatabaseError.fromException(e).extractMessage()) + } + } + private fun toSort(sort: CredentialSort): Expression<*> { return when (sort) { CredentialSort.NAME -> PasswordsTable.name diff --git a/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordInfoHeader.kt b/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordInfoHeader.kt index 82cd5ba..6f744e5 100644 --- a/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordInfoHeader.kt +++ b/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordInfoHeader.kt @@ -9,10 +9,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Star -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -94,9 +91,9 @@ fun PasswordInfoHeader(screenModel: SecVaultScreenModel) { Column( modifier = Modifier.weight(3f) - .fillMaxHeight() - .fillMaxWidth() - .padding(PaddingValues(top = 4.dp)), + .fillMaxHeight() + .fillMaxWidth() + .padding(PaddingValues(top = 4.dp)), horizontalAlignment = Alignment.End ) { @@ -106,12 +103,26 @@ fun PasswordInfoHeader(screenModel: SecVaultScreenModel) { ) { Column() { - Icon( - imageVector = Icons.Default.Star, - contentDescription = "User Icon", - modifier = Modifier.size(24.dp), - tint = Color.White + IconButton( + onClick = { + when (selectedMenu) { + DefaultMenuItem.PASSWORDS, + DefaultMenuItem.CREDIT_CARD, + DefaultMenuItem.NOTES -> { + screenModel.favorite(selectedCredential.getId()!!) + } + } + }, + enabled = selectedCredential.isSelected() ) + { + Icon( + imageVector = Icons.Default.Star, + contentDescription = "Favorite Icon", + modifier = Modifier.size(24.dp), + tint = if (selectedCredential.isFavorite()) Color.Yellow else Color.White + ) + } } Column() { @@ -120,14 +131,22 @@ fun PasswordInfoHeader(screenModel: SecVaultScreenModel) { val navigator = LocalNavigator.current OutlinedButton( - onClick = { when(selectedMenu) { - DefaultMenuItem.PASSWORDS -> {navigator?.push(PasswordMgntScreen(selectedCredential.password, MODIFIATION))} - DefaultMenuItem.CREDIT_CARD -> {navigator?.push(CreditCardForm(selectedCredential.creditCard, MODIFIATION))} - DefaultMenuItem.NOTES -> TODO() - } }, + onClick = { + when (selectedMenu) { + DefaultMenuItem.PASSWORDS -> { + navigator?.push(PasswordMgntScreen(selectedCredential.password, MODIFIATION)) + } + + DefaultMenuItem.CREDIT_CARD -> { + navigator?.push(CreditCardForm(selectedCredential.creditCard, MODIFIATION)) + } + + DefaultMenuItem.NOTES -> TODO() + } + }, enabled = selectedCredential.isSelected(), modifier = Modifier.size(height = 28.dp, width = 62.dp) - .hoverable(interactionSource), + .hoverable(interactionSource), shape = RoundedCornerShape(size = 4.dp), contentPadding = PaddingValues(4.dp), border = BorderStroke(2.dp, color = Color.White), diff --git a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt index db1920d..5d8eb6e 100644 --- a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt +++ b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt @@ -33,7 +33,7 @@ fun PasswordItem(credentialDisplay: CredentialDisplay, onClick: (id: UUID) -> Un Row( modifier = Modifier.height(60.dp).fillMaxWidth() .background( - if (isHovered) PasswordColors.tertiary else Color.Transparent, + if (isHovered || credentialDisplay.isSelected) PasswordColors.tertiary else Color.Transparent, shape = RoundedCornerShape(6.dp) ) .padding(PaddingValues(start = 5.dp, end = 5.dp)) diff --git a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt index bed9803..afa9aba 100644 --- a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt +++ b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt @@ -20,6 +20,7 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { val creditCards by screenModel.creditCardItems.collectAsState() val passwordItems by screenModel.passwordItems.collectAsState() val secVaultState by screenModel.secVaultState.collectAsState() + val selectedCredential by screenModel.selectedCredential.collectAsState() Column( modifier = Modifier.padding(PaddingValues(start = 20.dp, end = 20.dp, top = 24.dp, bottom = 20.dp)) @@ -67,7 +68,8 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { id = item.id, title = item.name, description = item.email?.takeIf { it.isNotEmpty() } ?: item.username!!, - favorite = item.favorite + favorite = item.favorite, + isSelected = selectedCredential.getId() == item.id ) }, screenModel @@ -83,6 +85,7 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { title = item.name, description = item.number, favorite = item.favorite, + isSelected = selectedCredential.getId() == item.id ) }, screenModel diff --git a/src/main/kotlin/viewmodel/SecVaultScreenModel.kt b/src/main/kotlin/viewmodel/SecVaultScreenModel.kt index 7f4a9bf..548f73f 100644 --- a/src/main/kotlin/viewmodel/SecVaultScreenModel.kt +++ b/src/main/kotlin/viewmodel/SecVaultScreenModel.kt @@ -13,8 +13,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch +import repository.creditcard.CreditCard import repository.creditcard.CreditCardRepository import repository.creditcard.projection.CreditCardSummary +import repository.password.Password import repository.password.PasswordRepository import repository.password.projection.PasswordSummary import java.util.* @@ -72,6 +74,8 @@ class SecVaultScreenModel( fun loadCredentials() { screenModelScope.launch(dispatcher) { _secVaultState.value = UiState.Loading + selectedCredential.value.getId()?.let { loadSelectedCredential(it)} + when (_selectedMenuItem.value) { DefaultMenuItem.PASSWORDS -> loadPasswords(_selectedSortItem.value) DefaultMenuItem.CREDIT_CARD -> loadCreditCards(_selectedSortItem.value) @@ -91,16 +95,18 @@ class SecVaultScreenModel( when (_selectedMenuItem.value) { DefaultMenuItem.PASSWORDS -> { passwordRepository.findById(id).let { result -> - if (result is Result.Success) { - _selectedCredential.value = SelectedCredential(result.data, null) + when (result) { + is Result.Error -> UiState.Error(result.message) + is Result.Success -> _selectedCredential.value = SelectedCredential(result.data, null) } } } DefaultMenuItem.CREDIT_CARD -> { creditCardRepository.findById(id).let { result -> - if (result is Result.Success) { - _selectedCredential.value = SelectedCredential(null, result.data) + when (result) { + is Result.Error -> UiState.Error(result.message) + is Result.Success -> _selectedCredential.value = SelectedCredential(null, result.data) } } } @@ -110,6 +116,21 @@ class SecVaultScreenModel( } } + fun favorite(id: UUID) { + screenModelScope.launch(dispatcher) { + val result = when (selectedMenuItem.value) { + DefaultMenuItem.PASSWORDS -> passwordRepository.favorite(id, appState.userName) + DefaultMenuItem.CREDIT_CARD -> creditCardRepository.favorite(id, appState.userName) + DefaultMenuItem.NOTES -> TODO() + } + + when (result) { + is Result.Error -> UiState.Error(result.message) + is Result.Success -> onScreenShown() + } + } + } + private suspend fun loadPasswords(sort: CredentialSort) { val criteria = CredentialSearchCriteria(appState.getAuthenticatedUser?.id?.value, sort) _passwordItems.value = when (val passwords = passwordRepository.findSummaries(criteria)) {