Skip to content
This repository was archived by the owner on Aug 21, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package com.gravatar.app.homeUi.presentation.home.share

internal sealed class ShareEvent {
data class OnEmailValueChanged(val value: String) : ShareEvent()
data class OnEmailSharingChanged(val isShared: Boolean) : ShareEvent()
data class OnPhoneValueChanged(val value: String) : ShareEvent()
data class OnPhoneSharingChanged(val isShared: Boolean) : ShareEvent()
data class OnUserSharePreferencesChanged(val shareFieldType: ShareFieldType) : ShareEvent()
data object OnAboutAppClicked : ShareEvent()
data object OnDismissAboutAppDialog : ShareEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ internal fun ShareScreen(uiState: ShareUiState, onEvent: (ShareEvent) -> Unit) {
.fillMaxWidth(),
)
SharePrivateContactInfo(
privateContactInfo = uiState.privateContactInfo,
privateContactState = uiState.privateContactState,
onEmailValueChange = { onEvent(ShareEvent.OnEmailValueChanged(it)) },
onEmailSwitchCheckedChange = { onEvent(ShareEvent.OnEmailSharingChanged(it)) },
onEmailSwitchCheckedChange = {
onEvent(ShareEvent.OnUserSharePreferencesChanged(ShareFieldType.PrivateEmail(it)))
},
onPhoneValueChange = { onEvent(ShareEvent.OnPhoneValueChanged(it)) },
onPhoneSwitchCheckedChange = { onEvent(ShareEvent.OnPhoneSharingChanged(it)) },
onPhoneSwitchCheckedChange = {
onEvent(ShareEvent.OnUserSharePreferencesChanged(ShareFieldType.PrivatePhone(it)))
},
modifier = Modifier.padding(16.dp),
)
ItemDivider()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
package com.gravatar.app.homeUi.presentation.home.share

import com.gravatar.app.usercomponent.domain.model.PrivateContactInfo
import com.gravatar.app.usercomponent.domain.model.UserSharePreferences
import com.gravatar.restapi.models.Profile

internal data class ShareUiState(
val profile: Profile? = null,
val avatarUrl: String? = null,
val isAboutAppDialogVisible: Boolean = false,
val privateContactInfo: PrivateContactInfo = PrivateContactInfo(),
val userSharePreferences: UserSharePreferences = UserSharePreferences(
name = true,
location = true,
title = true,
organization = true,
description = true,
profileUrl = true
)
val privateContactInfo: PrivateContactInfo = PrivateContactInfo.Default,
val userSharePreferences: UserSharePreferences = UserSharePreferences.Default
) {

val privateContactState = PrivateContactState(
emailValue = privateContactInfo.privateEmail,
isEmailShared = userSharePreferences.privateEmail,
phoneValue = privateContactInfo.privatePhone,
isPhoneShared = userSharePreferences.privatePhone,
)

fun copyWithUserSharePreferences(
shareFieldType: ShareFieldType,
): ShareUiState = this.copy(
userSharePreferences = userSharePreferences.copy(
privateEmail = if (shareFieldType is ShareFieldType.PrivateEmail) shareFieldType.checked else userSharePreferences.privateEmail,
privatePhone = if (shareFieldType is ShareFieldType.PrivatePhone) shareFieldType.checked else userSharePreferences.privatePhone,
name = if (shareFieldType is ShareFieldType.Name) shareFieldType.checked else userSharePreferences.name,
location = if (shareFieldType is ShareFieldType.Location) shareFieldType.checked else userSharePreferences.location,
title = if (shareFieldType is ShareFieldType.Title) shareFieldType.checked else userSharePreferences.title,
Expand All @@ -31,7 +35,7 @@ internal data class ShareUiState(
)
}

internal data class PrivateContactInfo(
internal data class PrivateContactState(
val emailValue: String = "",
val isEmailShared: Boolean = false,
val phoneValue: String = "",
Expand Down Expand Up @@ -64,4 +68,12 @@ internal sealed class ShareFieldType {
data class ProfileUrl(
override val checked: Boolean
) : ShareFieldType()

data class PrivateEmail(
override val checked: Boolean
) : ShareFieldType()

data class PrivatePhone(
override val checked: Boolean
) : ShareFieldType()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gravatar.app.usercomponent.domain.repository.UserRepository
import com.gravatar.app.usercomponent.domain.usecase.GetAvatarUrl
import com.gravatar.app.usercomponent.domain.usecase.GetUserSharePreferences
import com.gravatar.app.usercomponent.domain.usecase.UpdateUserSharePreferences
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class ShareViewModel(
private val userRepository: UserRepository,
private val getAvatarUrl: GetAvatarUrl,
private val getUserSharePreferences: GetUserSharePreferences,
private val updateUserSharePreferences: UpdateUserSharePreferences,
) : ViewModel() {

private val _uiState = MutableStateFlow(ShareUiState())
Expand All @@ -22,6 +27,7 @@ internal class ShareViewModel(
init {
collectProfile()
collectAvatarUrl()
collectUserSharePreferences()
}

fun onEvent(shareEvent: ShareEvent) {
Expand All @@ -30,17 +36,7 @@ internal class ShareViewModel(
_uiState.update {
it.copy(
privateContactInfo = it.privateContactInfo.copy(
emailValue = shareEvent.value
)
)
}
}

is ShareEvent.OnEmailSharingChanged -> {
_uiState.update {
it.copy(
privateContactInfo = it.privateContactInfo.copy(
isEmailShared = shareEvent.isShared
privateEmail = shareEvent.value
)
)
}
Expand All @@ -50,31 +46,26 @@ internal class ShareViewModel(
_uiState.update {
it.copy(
privateContactInfo = it.privateContactInfo.copy(
phoneValue = shareEvent.value
)
)
}
}

is ShareEvent.OnPhoneSharingChanged -> {
_uiState.update {
it.copy(
privateContactInfo = it.privateContactInfo.copy(
isPhoneShared = shareEvent.isShared
privatePhone = shareEvent.value
)
)
}
}

is ShareEvent.OnAboutAppClicked -> showAboutAppDialog()
is ShareEvent.OnDismissAboutAppDialog -> hideAboutAppDialog()
is ShareEvent.OnUserSharePreferencesChanged -> updateUserSharePreferences(shareEvent.shareFieldType)
is ShareEvent.OnUserSharePreferencesChanged -> handleUserSharePreferencesChange(shareEvent.shareFieldType)
}
}

private fun updateUserSharePreferences(shareFieldType: ShareFieldType) {
_uiState.update { currentState ->
currentState.copyWithUserSharePreferences(shareFieldType)
private fun handleUserSharePreferencesChange(shareFieldType: ShareFieldType) {
with(_uiState.value.copyWithUserSharePreferences(shareFieldType)) {
// update the UI state with the new preferences
_uiState.value = this
// Save the updated preferences
viewModelScope.launch {
updateUserSharePreferences(this@with.userSharePreferences)
}
}
}

Expand Down Expand Up @@ -113,4 +104,16 @@ internal class ShareViewModel(
}
.launchIn(viewModelScope)
}

private fun collectUserSharePreferences() {
getUserSharePreferences()
.onEach { preferences ->
_uiState.update { currentState ->
currentState.copy(
userSharePreferences = preferences
)
}
}
.launchIn(viewModelScope)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.gravatar.app.homeUi.R
import com.gravatar.app.homeUi.presentation.home.share.PrivateContactInfo
import com.gravatar.app.homeUi.presentation.home.share.PrivateContactState

@Composable
internal fun SharePrivateContactInfo(
privateContactInfo: PrivateContactInfo,
privateContactState: PrivateContactState,
onEmailValueChange: (String) -> Unit,
onEmailSwitchCheckedChange: (Boolean) -> Unit,
onPhoneValueChange: (String) -> Unit,
Expand All @@ -29,17 +29,17 @@ internal fun SharePrivateContactInfo(
)
ShareEditableField(
placeholder = R.string.share_tab_private_contact_email_placeholder,
value = privateContactInfo.emailValue,
value = privateContactState.emailValue,
onValueChange = onEmailValueChange,
switchChecked = privateContactInfo.isEmailShared,
switchChecked = privateContactState.isEmailShared,
keyboardType = KeyboardType.Email,
onSwitchCheckedChange = onEmailSwitchCheckedChange,
)
ShareEditableField(
placeholder = R.string.share_tab_private_contact_phone_number_placeholder,
value = privateContactInfo.phoneValue,
value = privateContactState.phoneValue,
onValueChange = onPhoneValueChange,
switchChecked = privateContactInfo.isPhoneShared,
switchChecked = privateContactState.isPhoneShared,
keyboardType = KeyboardType.Phone,
onSwitchCheckedChange = onPhoneSwitchCheckedChange,
)
Expand All @@ -50,7 +50,7 @@ internal fun SharePrivateContactInfo(
@Composable
private fun SharePrivateContactInfoPreview() {
SharePrivateContactInfo(
privateContactInfo = PrivateContactInfo(
privateContactState = PrivateContactState(
emailValue = "example@email.com",
isEmailShared = true,
phoneValue = "123-456-7890",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,7 @@ private fun SharePublicContactInfoPreview() {
GravatarAppTheme {
SharePublicContactInfo(
profile = defaultProfile(hash = "hash"),
userSharePreferences = UserSharePreferences(
name = true,
location = true,
title = true,
organization = true,
description = true,
profileUrl = true
),
userSharePreferences = UserSharePreferences.Default,
onUserPreferenceChanged = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package com.gravatar.app.homeUi.presentation.home.share

import app.cash.turbine.test
import com.gravatar.app.testUtils.CoroutineTestRule
import com.gravatar.app.usercomponent.domain.model.UserSharePreferences
import com.gravatar.app.usercomponent.domain.repository.UserRepository
import com.gravatar.app.usercomponent.domain.usecase.GetAvatarUrl
import com.gravatar.app.usercomponent.domain.usecase.GetUserSharePreferences
import com.gravatar.app.usercomponent.domain.usecase.UpdateUserSharePreferences
import com.gravatar.restapi.models.Profile
import com.gravatar.restapi.models.ProfileContactInfo
import io.mockk.every
Expand Down Expand Up @@ -32,17 +35,26 @@ class ShareViewModelTest {
private val getAvatarUrl: GetAvatarUrl = object : GetAvatarUrl {
override fun invoke() = avatarUrlFlow
}
private val getUserSharePreferences: GetUserSharePreferences = object : GetUserSharePreferences {
override fun invoke() = userSharePreferencesFlow
}
private val updateUserSharePreferences = object : UpdateUserSharePreferences {
override suspend fun invoke(userSharePreferences: UserSharePreferences) {
userSharePreferencesFlow.emit(userSharePreferences)
}
}
private val userRepository = mockk<UserRepository>()

private lateinit var viewModel: ShareViewModel

private val avatarUrlFlow: MutableSharedFlow<URL?> = MutableSharedFlow()
private val profileFlow: MutableSharedFlow<Profile?> = MutableSharedFlow()
private val userSharePreferencesFlow: MutableSharedFlow<UserSharePreferences> = MutableSharedFlow()

@Before
fun setup() {
every { userRepository.getProfile() } returns profileFlow
viewModel = ShareViewModel(userRepository, getAvatarUrl)
viewModel = ShareViewModel(userRepository, getAvatarUrl, getUserSharePreferences, updateUserSharePreferences)
}

@Test
Expand Down Expand Up @@ -79,21 +91,7 @@ class ShareViewModelTest {

// Then
viewModel.uiState.test {
assertEquals(newEmailValue, awaitItem().privateContactInfo.emailValue)
}
}

@Test
fun `when OnEmailSharingChanged event is triggered then isEmailShared is updated`() = runTest {
// Given
val isShared = true

// When
viewModel.onEvent(ShareEvent.OnEmailSharingChanged(isShared))

// Then
viewModel.uiState.test {
assertEquals(isShared, awaitItem().privateContactInfo.isEmailShared)
assertEquals(newEmailValue, awaitItem().privateContactState.emailValue)
}
}

Expand All @@ -107,21 +105,7 @@ class ShareViewModelTest {

// Then
viewModel.uiState.test {
assertEquals(newPhoneValue, awaitItem().privateContactInfo.phoneValue)
}
}

@Test
fun `when OnPhoneSharingChanged event is triggered then isPhoneShared is updated`() = runTest {
// Given
val isShared = true

// When
viewModel.onEvent(ShareEvent.OnPhoneSharingChanged(isShared))

// Then
viewModel.uiState.test {
assertEquals(isShared, awaitItem().privateContactInfo.isPhoneShared)
assertEquals(newPhoneValue, awaitItem().privateContactState.phoneValue)
}
}

Expand Down Expand Up @@ -298,6 +282,45 @@ class ShareViewModelTest {
}
}

@Test
fun `when user share preferences are emitted then uiState is updated with preferences`() = runTest {
// Given
val testPreferences = UserSharePreferences.Default.copy(
name = false,
title = false,
description = false,
)

// When
userSharePreferencesFlow.emit(testPreferences)
advanceUntilIdle()

// Then
viewModel.uiState.test {
val state = awaitItem()
assertEquals(testPreferences, state.userSharePreferences)
}
}

@Test
fun `when user share preferences are changed then updateUserSharePreferences is called`() = runTest {
// Given
val initialState = viewModel.uiState.value
val newNamePreference = !initialState.userSharePreferences.name
val shareFieldType = ShareFieldType.Name(checked = newNamePreference)

userSharePreferencesFlow.test {
// When
viewModel.onEvent(ShareEvent.OnUserSharePreferencesChanged(shareFieldType))

// Then
val expectedSharePreferences = initialState.userSharePreferences.copy(
name = newNamePreference,
)
assertEquals(expectedSharePreferences, awaitItem())
}
}

private fun createTestProfile() = Profile {
hash = "test-hash"
displayName = "Test User"
Expand Down
Loading