Skip to content

Commit d0f2a1e

Browse files
committed
[fix] #79 프로필 이미지 변경 후 이미지 로드되기 전 깜빡이며 뒤로가는 현상 수정
1 parent 53e3f99 commit d0f2a1e

5 files changed

Lines changed: 63 additions & 49 deletions

File tree

feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageContract.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package com.neki.android.feature.mypage.impl.main
22

3-
import android.net.Uri
43
import com.neki.android.core.model.UserInfo
54
import com.neki.android.feature.mypage.impl.main.const.ServiceInfoMenu
65
import com.neki.android.feature.mypage.impl.permission.const.NekiPermission
7-
import com.neki.android.feature.mypage.impl.profile.model.SelectedProfileImage
6+
import com.neki.android.feature.mypage.impl.profile.model.EditProfileImageType
87

98
data class MyPageState(
109
val isLoading: Boolean = false,
1110
val userInfo: UserInfo = UserInfo(),
1211
val appVersion: String = "",
13-
val selectedProfileImage: SelectedProfileImage = SelectedProfileImage.NoChange,
12+
val profileImageState: EditProfileImageType = EditProfileImageType.OriginalImageUrl(""),
1413
val isShowLogoutDialog: Boolean = false,
1514
val isShowWithdrawDialog: Boolean = false,
1615
val isShowImageChooseDialog: Boolean = false,
@@ -39,11 +38,8 @@ sealed interface MyPageIntent {
3938
data object ClickEditIcon : MyPageIntent
4039
data object ClickCameraIcon : MyPageIntent
4140
data object DismissImageChooseDialog : MyPageIntent
42-
data class SelectProfileImage(val image: SelectedProfileImage) : MyPageIntent
43-
data class ClickEditComplete(
44-
val nickname: String,
45-
val uri: Uri?,
46-
) : MyPageIntent
41+
data class SelectProfileImage(val image: EditProfileImageType) : MyPageIntent
42+
data class ClickEditComplete(val nickname: String) : MyPageIntent
4743
data object ClickLogout : MyPageIntent
4844
data object DismissLogoutDialog : MyPageIntent
4945
data object ConfirmLogout : MyPageIntent
@@ -72,4 +68,5 @@ sealed interface MyPageEffect {
7268
data object OpenOssLicenses : MyPageEffect
7369
data object LogoutWithKakao : MyPageEffect
7470
data object UnlinkWithKakao : MyPageEffect
71+
data class PreloadProfileImage(val url: String) : MyPageEffect
7572
}

feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
package com.neki.android.feature.mypage.impl.main
22

3-
import android.content.Context
43
import androidx.lifecycle.ViewModel
54
import androidx.lifecycle.viewModelScope
6-
import coil3.imageLoader
7-
import coil3.request.ImageRequest
85
import com.neki.android.core.dataapi.repository.AuthRepository
96
import com.neki.android.core.dataapi.repository.TokenRepository
107
import com.neki.android.core.dataapi.repository.UserRepository
118
import com.neki.android.core.domain.usecase.UploadProfileImageUseCase
129
import com.neki.android.core.ui.MviIntentStore
1310
import com.neki.android.core.ui.mviIntentStore
1411
import com.neki.android.feature.mypage.impl.permission.const.NekiPermission
15-
import com.neki.android.feature.mypage.impl.profile.model.SelectedProfileImage
12+
import com.neki.android.feature.mypage.impl.profile.model.EditProfileImageType
1613
import dagger.hilt.android.lifecycle.HiltViewModel
17-
import dagger.hilt.android.qualifiers.ApplicationContext
1814
import kotlinx.coroutines.async
1915
import kotlinx.coroutines.awaitAll
2016
import kotlinx.coroutines.launch
@@ -23,7 +19,6 @@ import javax.inject.Inject
2319

2420
@HiltViewModel
2521
internal class MyPageViewModel @Inject constructor(
26-
@ApplicationContext private val context: Context,
2722
private val uploadProfileImageUseCase: UploadProfileImageUseCase,
2823
private val userRepository: UserRepository,
2924
private val authRepository: AuthRepository,
@@ -55,18 +50,18 @@ internal class MyPageViewModel @Inject constructor(
5550

5651
// Profile
5752
MyPageIntent.ClickBackIcon -> {
58-
reduce { copy(selectedProfileImage = SelectedProfileImage.NoChange) }
53+
reduce { copy(profileImageState = EditProfileImageType.OriginalImageUrl(state.userInfo.profileImageUrl)) }
5954
postSideEffect(MyPageEffect.NavigateBack)
6055
}
6156

6257
MyPageIntent.ClickEditIcon -> postSideEffect(MyPageEffect.NavigateToEditProfile)
6358
MyPageIntent.ClickCameraIcon -> reduce { copy(isShowImageChooseDialog = true) }
6459
MyPageIntent.DismissImageChooseDialog -> reduce { copy(isShowImageChooseDialog = false) }
65-
is MyPageIntent.SelectProfileImage -> reduce { copy(selectedProfileImage = intent.image, isShowImageChooseDialog = false) }
60+
is MyPageIntent.SelectProfileImage -> reduce { copy(profileImageState = intent.image, isShowImageChooseDialog = false) }
6661
is MyPageIntent.ClickEditComplete -> {
6762
val isNicknameChanged = state.userInfo.nickname != intent.nickname
68-
val isProfileImageChanged = state.selectedProfileImage != SelectedProfileImage.NoChange
69-
updateProfile(intent.nickname, intent.uri, isNicknameChanged, isProfileImageChanged, reduce, postSideEffect)
63+
val isProfileImageChanged = state.profileImageState !is EditProfileImageType.OriginalImageUrl
64+
updateProfile(state, intent.nickname, isNicknameChanged, isProfileImageChanged, reduce, postSideEffect)
7065
}
7166

7267
MyPageIntent.ClickLogout -> reduce { copy(isShowLogoutDialog = true) }
@@ -132,15 +127,14 @@ internal class MyPageViewModel @Inject constructor(
132127
}
133128

134129
private fun updateProfile(
130+
state: MyPageState,
135131
nickname: String,
136-
uri: android.net.Uri?,
137132
isNicknameChanged: Boolean,
138133
isProfileImageChanged: Boolean,
139134
reduce: (MyPageState.() -> MyPageState) -> Unit,
140135
postSideEffect: (MyPageEffect) -> Unit,
141136
) = viewModelScope.launch {
142137
if (!isNicknameChanged && !isProfileImageChanged) {
143-
reduce { copy(selectedProfileImage = SelectedProfileImage.NoChange) }
144138
postSideEffect(MyPageEffect.NavigateBack)
145139
return@launch
146140
}
@@ -149,29 +143,33 @@ internal class MyPageViewModel @Inject constructor(
149143

150144
buildList {
151145
if (isNicknameChanged) add(async { userRepository.updateUserInfo(nickname = nickname) })
152-
if (isProfileImageChanged) add(async { uploadProfileImageUseCase(uri = uri) })
146+
if (isProfileImageChanged) {
147+
val uri = (state.profileImageState as? EditProfileImageType.ImageUri)?.uri
148+
add(async { uploadProfileImageUseCase(uri = uri) })
149+
}
153150
}.awaitAll()
154151

155152
userRepository.getUserInfo()
156153
.onSuccess { user ->
157154
if (isProfileImageChanged) {
158-
val request = ImageRequest.Builder(context)
159-
.data(user.profileImageUrl)
160-
.build()
161-
context.imageLoader.execute(request)
155+
postSideEffect(MyPageEffect.PreloadProfileImage(user.profileImageUrl))
162156
}
163-
164157
reduce {
165158
copy(
166159
isLoading = false,
167-
selectedProfileImage = SelectedProfileImage.NoChange,
160+
profileImageState = EditProfileImageType.OriginalImageUrl(user.profileImageUrl),
168161
userInfo = user,
169162
)
170163
}
171-
postSideEffect(MyPageEffect.NavigateBack)
172164
}
173165
.onFailure {
174-
reduce { copy(isLoading = false, selectedProfileImage = SelectedProfileImage.NoChange) }
166+
Timber.e(it)
167+
reduce {
168+
copy(
169+
isLoading = false,
170+
profileImageState = EditProfileImageType.OriginalImageUrl(state.userInfo.profileImageUrl),
171+
)
172+
}
175173
postSideEffect(MyPageEffect.NavigateBack)
176174
}
177175
}

feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ import androidx.activity.compose.rememberLauncherForActivityResult
2020
import androidx.activity.result.PickVisualMediaRequest
2121
import androidx.activity.result.contract.ActivityResultContracts
2222
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.LaunchedEffect
2324
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
2428
import androidx.compose.ui.Alignment
29+
import androidx.compose.ui.platform.LocalContext
2530
import androidx.compose.ui.Modifier
2631
import androidx.compose.ui.graphics.SolidColor
2732
import androidx.compose.ui.unit.dp
@@ -31,11 +36,14 @@ import com.neki.android.core.designsystem.ComponentPreview
3136
import com.neki.android.core.designsystem.ui.theme.NekiTheme
3237
import com.neki.android.core.ui.component.LoadingDialog
3338
import com.neki.android.core.ui.compose.collectWithLifecycle
39+
import coil3.imageLoader
40+
import coil3.request.ImageRequest
41+
import com.neki.android.core.designsystem.R
3442
import com.neki.android.feature.mypage.impl.main.MyPageEffect
3543
import com.neki.android.feature.mypage.impl.main.MyPageIntent
3644
import com.neki.android.feature.mypage.impl.main.MyPageState
3745
import com.neki.android.feature.mypage.impl.main.MyPageViewModel
38-
import com.neki.android.feature.mypage.impl.profile.model.SelectedProfileImage
46+
import com.neki.android.feature.mypage.impl.profile.model.EditProfileImageType
3947
import com.neki.android.feature.mypage.impl.profile.component.EditProfileImage
4048
import com.neki.android.feature.mypage.impl.profile.component.ProfileEditTopBar
4149
import com.neki.android.feature.mypage.impl.profile.component.ProfileImageChooseDialog
@@ -46,11 +54,19 @@ internal fun EditProfileRoute(
4654
viewModel: MyPageViewModel = hiltViewModel(),
4755
navigateBack: () -> Unit,
4856
) {
57+
val context = LocalContext.current
4958
val uiState by viewModel.store.uiState.collectAsStateWithLifecycle()
5059

5160
viewModel.store.sideEffects.collectWithLifecycle { sideEffect ->
5261
when (sideEffect) {
5362
MyPageEffect.NavigateBack -> navigateBack()
63+
is MyPageEffect.PreloadProfileImage -> {
64+
val request = ImageRequest.Builder(context)
65+
.data(sideEffect.url)
66+
.build()
67+
context.imageLoader.execute(request)
68+
navigateBack()
69+
}
5470
else -> {}
5571
}
5672
}
@@ -66,16 +82,23 @@ fun EditProfileScreen(
6682
uiState: MyPageState = MyPageState(),
6783
onIntent: (MyPageIntent) -> Unit = {},
6884
) {
69-
val displayProfileImage: Any? = when (uiState.selectedProfileImage) {
70-
SelectedProfileImage.NoChange -> uiState.userInfo.profileImageUrl
71-
is SelectedProfileImage.Selected -> uiState.selectedProfileImage.uri
85+
var displayProfileImage by remember {
86+
mutableStateOf<Any?>(uiState.userInfo.profileImageUrl)
87+
}
88+
89+
LaunchedEffect(uiState.profileImageState) {
90+
when (uiState.profileImageState) {
91+
is EditProfileImageType.OriginalImageUrl -> {}
92+
is EditProfileImageType.ImageUri -> displayProfileImage = uiState.profileImageState.uri
93+
EditProfileImageType.Default -> displayProfileImage = R.drawable.image_empty_profile_image
94+
}
7295
}
7396

7497
val textFieldState = rememberTextFieldState(uiState.userInfo.nickname)
7598

7699
val photoPicker = rememberLauncherForActivityResult(contract = ActivityResultContracts.PickVisualMedia()) { uri ->
77100
if (uri != null) {
78-
onIntent(MyPageIntent.SelectProfileImage(SelectedProfileImage.Selected(uri)))
101+
onIntent(MyPageIntent.SelectProfileImage(EditProfileImageType.ImageUri(uri)))
79102
} else {
80103
Timber.d("No media selected")
81104
}
@@ -89,12 +112,7 @@ fun EditProfileScreen(
89112
enabled = textFieldState.text.isNotEmpty(),
90113
onBack = { onIntent(MyPageIntent.ClickBackIcon) },
91114
onClickComplete = {
92-
onIntent(
93-
MyPageIntent.ClickEditComplete(
94-
nickname = textFieldState.text.toString(),
95-
uri = (uiState.selectedProfileImage as? SelectedProfileImage.Selected)?.uri,
96-
),
97-
)
115+
onIntent(MyPageIntent.ClickEditComplete(nickname = textFieldState.text.toString()))
98116
},
99117
)
100118
EditProfileImage(
@@ -160,7 +178,7 @@ fun EditProfileScreen(
160178
if (uiState.isShowImageChooseDialog) {
161179
ProfileImageChooseDialog(
162180
onDismissRequest = { onIntent(MyPageIntent.DismissImageChooseDialog) },
163-
onClickDefaultProfile = { onIntent(MyPageIntent.SelectProfileImage(SelectedProfileImage.Selected(null))) },
181+
onClickDefaultProfile = { onIntent(MyPageIntent.SelectProfileImage(EditProfileImageType.Default)) },
164182
onClickSelectPhoto = {
165183
onIntent(MyPageIntent.DismissImageChooseDialog)
166184
photoPicker.launch(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.neki.android.feature.mypage.impl.profile.model
2+
3+
import android.net.Uri
4+
5+
sealed interface EditProfileImageType {
6+
data class OriginalImageUrl(val url: String) : EditProfileImageType
7+
data class ImageUri(val uri: Uri) : EditProfileImageType
8+
data object Default : EditProfileImageType
9+
}

feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/model/SelectedProfileImage.kt

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)