Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#43 프로필 기능을 개발한다 #63

Merged
merged 36 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
35f1960
feat: MyPage 뷰 구성
rhkrwngud445 Mar 20, 2024
6416db1
feat: profile editor 모듈 추가
rhkrwngud445 Mar 20, 2024
7cd610d
feat: profile editor UI 구현
rhkrwngud445 Mar 21, 2024
5a252a8
feat: 갤러리 이동 기능 추가 및 ProfileEditor 도메인 생성
rhkrwngud445 Mar 21, 2024
33473c9
feat:topappbar window inset 추가
rhkrwngud445 Mar 24, 2024
6b56f9b
feat: 마이페이지 화면 API 연동
rhkrwngud445 Mar 25, 2024
fac4011
feat: 닉네임 네비게이션 전달 로직 추가
rhkrwngud445 Mar 25, 2024
4533b35
feat: 프로필 변경 여부 확인 로직 추가
rhkrwngud445 Mar 25, 2024
d9ab4a2
feat: 저장여부 dialog 추가
rhkrwngud445 Mar 25, 2024
92cb9f8
feat: 닉네임 검증로직 추가
rhkrwngud445 Mar 26, 2024
9cb9dee
feat: 프로필 변경 API 연동
rhkrwngud445 Mar 26, 2024
103b89e
fix: 데이터 오류시 기본 닉네임 설정
rhkrwngud445 Mar 27, 2024
59094ea
fix: 프로필 Put 요청 버그 수정
rhkrwngud445 Mar 27, 2024
ba3cd48
feat: changedProfile domain 모델 추가
rhkrwngud445 Mar 27, 2024
0517e42
feat: ProfileEdit UiModel 적용
rhkrwngud445 Mar 29, 2024
af3658a
feat: ProfileInfo UiModel 적용
rhkrwngud445 Mar 29, 2024
4d1c73c
faet: domain nickname format exception 추가
rhkrwngud445 Mar 29, 2024
e314e63
refactor: image part 중복로직 함수화
rhkrwngud445 Mar 29, 2024
a519f54
refactor: verifyNickname response boolean으로 수정 및 필요없는 emit로직 제거
rhkrwngud445 Mar 30, 2024
c0562d3
refactor: changingProfileStatus를 통해 값을 변경상태를 확인하도록 변경
rhkrwngud445 Mar 30, 2024
b6cb17f
refactor: getProfile에서 onError suspend 적용 및 부수효과에 대해 channel로 변경
rhkrwngud445 Mar 30, 2024
2a7411f
refactor: viewModelScope 중복 제거
rhkrwngud445 Mar 30, 2024
ef5c4c1
refactor: onError() suspend로 변경
rhkrwngud445 Mar 30, 2024
e1afaa9
refactor: 분기가 3개이상일시에 if-> when을 사용
rhkrwngud445 Mar 31, 2024
4a27116
refactor: suspend onError 사용
rhkrwngud445 Mar 31, 2024
1958090
refactor: nickname check(require) 제거
rhkrwngud445 Mar 31, 2024
3ba2d2e
refactor: uiState에 isChanged 컬럼 추가
rhkrwngud445 Mar 31, 2024
a77266d
refactor: nickname validate를 state를 바꾸게 변경
rhkrwngud445 Mar 31, 2024
257f63c
refactor: 서버 errorbody를 처리하는 로직 추가
rhkrwngud445 Mar 31, 2024
2e797ec
refactor: 변경여부에 따라 화면 표시 분기 추가
rhkrwngud445 Mar 31, 2024
6947606
feat: 로그아웃 기능 추가
rhkrwngud445 Mar 31, 2024
8210a81
fix: 분기처리 수정
rhkrwngud445 Mar 31, 2024
0a8ad94
fix: 피드백 수정
rhkrwngud445 Apr 1, 2024
20d75da
Merge branch 'develop' into feat/#43-프로필_기능을_개발한다
rhkrwngud445 Apr 1, 2024
c4cfcfc
fix: 안쓰는 패키지 제거
rhkrwngud445 Apr 1, 2024
2d8a150
refactor: 결과값을 통한 프로필 갱신 및 리뷰 반영
rhkrwngud445 Apr 1, 2024
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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation(project(":feature:mypage"))
implementation(project(":feature:registerpost"))
implementation(project(":feature:gallery"))
implementation(project(":feature:profileeditor"))
implementation(project(":core:interceptor"))
implementation(project(":core:data"))
implementation(project(":core:network"))
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.withpeace.withpeace

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
Expand Down Expand Up @@ -46,7 +44,8 @@ fun WithpeaceApp(
}
}
},
modifier = Modifier.fillMaxSize().safeDrawingPadding(),
modifier = Modifier
.fillMaxSize(),
snackbarHost = { SnackbarHost(snackBarHostState) },
) { innerPadding ->
WithpeaceNavHost(
Expand Down
45 changes: 37 additions & 8 deletions app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package com.withpeace.withpeace.navigation

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.withpeace.withpeace.feature.home.navigation.homeNavGraph
import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph
import com.withpeace.withpeace.feature.post.navigation.postNavGraph
import com.app.profileeditor.navigation.navigateProfileEditor
import com.app.profileeditor.navigation.profileEditorNavGraph
import com.withpeace.withpeace.feature.gallery.navigation.galleryNavGraph
import com.withpeace.withpeace.feature.gallery.navigation.navigateToGallery
import com.withpeace.withpeace.feature.home.navigation.homeNavGraph
import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE
import com.withpeace.withpeace.feature.login.navigation.loginNavGraph
import com.withpeace.withpeace.feature.login.navigation.navigateLogin
import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph
import com.withpeace.withpeace.feature.post.navigation.postNavGraph
import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT
import com.withpeace.withpeace.feature.registerpost.navigation.registerPostNavGraph

Expand Down Expand Up @@ -60,6 +59,36 @@ fun WithpeaceNavHost(
)
homeNavGraph(onShowSnackBar)
postNavGraph(onShowSnackBar)
myPageNavGraph(onShowSnackBar)
myPageNavGraph(
onShowSnackBar = onShowSnackBar,
onEditProfile = { nickname, profileImageUrl ->
navController.navigateProfileEditor(
nickname = nickname,
profileImageUrl = profileImageUrl,
)
},
onLogoutSuccess = {
navController.navigateLogin(
navOptions = navOptions {
popUpTo(navController.graph.id) {
inclusive = true
}
},
)
},
onWithdrawClick = {},
)
profileEditorNavGraph(
onShowSnackBar = onShowSnackBar,
onClickBackButton = {
navController.popBackStack()
},
onNavigateToGallery = {
navController.navigateToGallery(imageLimit = 1)
},
onUpdateSuccess = {
navController.popBackStack()
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package com.withpeace.withpeace.core.data.di
import com.withpeace.withpeace.core.data.repository.DefaultImageRepository
import com.withpeace.withpeace.core.data.repository.DefaultPostRepository
import com.withpeace.withpeace.core.data.repository.DefaultTokenRepository
import com.withpeace.withpeace.core.data.repository.DefaultUserRepository
import com.withpeace.withpeace.core.domain.repository.ImageRepository
import com.withpeace.withpeace.core.domain.repository.PostRepository
import com.withpeace.withpeace.core.domain.repository.TokenRepository
import com.withpeace.withpeace.core.domain.repository.UserRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -27,4 +29,10 @@ interface RepositoryModule {
@Binds
@Singleton
fun bindsPostRepository(defaultPostRepository: DefaultPostRepository): PostRepository

@Binds
@Singleton
fun bindsUserRepository(
defaultUserRepository: DefaultUserRepository,
): UserRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.withpeace.withpeace.core.data.mapper

import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile
import com.withpeace.withpeace.core.domain.model.profile.Nickname
import com.withpeace.withpeace.core.network.di.response.ChangedProfileResponse

fun ChangedProfileResponse.toDomain(): ChangedProfile {
return ChangedProfile(
nickname = Nickname.create(this.nickname),
profileImageUrl = profileImageUrl,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.withpeace.withpeace.core.data.mapper

import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo
import com.withpeace.withpeace.core.network.di.response.ProfileResponse

fun ProfileResponse.toDomain(): ProfileInfo {
return ProfileInfo(
nickname = nickname,
profileImageUrl = profileImageUrl,
email = email,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import javax.inject.Inject
class DefaultTokenRepository @Inject constructor(
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
private val authService: AuthService,
private val userService: UserService,
) : TokenRepository {
override suspend fun isLogin(): Boolean {
val token = tokenPreferenceDataSource.accessToken.firstOrNull()
Expand All @@ -30,7 +29,7 @@ class DefaultTokenRepository @Inject constructor(
nickname: String,
onError: (String) -> Unit,
): Flow<Unit> = flow {
userService.signUp(
authService.signUp(
SignUpRequest(email = email, nickname = nickname, deviceToken = null),
).suspendMapSuccess {
val data = this.data
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.withpeace.withpeace.core.data.repository

import android.content.Context
import android.net.Uri
import com.skydoves.sandwich.messageOrNull
import com.skydoves.sandwich.suspendMapSuccess
import com.skydoves.sandwich.suspendOnError
import com.skydoves.sandwich.suspendOnException
import com.withpeace.withpeace.core.data.mapper.toDomain
import com.withpeace.withpeace.core.data.util.convertToFile
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.WithPeaceError
import com.withpeace.withpeace.core.domain.model.profile.Nickname
import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo
import com.withpeace.withpeace.core.domain.repository.UserRepository
import com.withpeace.withpeace.core.network.di.common.getErrorBody
import com.withpeace.withpeace.core.network.di.request.NicknameRequest
import com.withpeace.withpeace.core.network.di.service.UserService
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import javax.inject.Inject

class DefaultUserRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val userService: UserService,
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
) : UserRepository {
override fun getProfile(
onError: suspend (WithPeaceError) -> Unit,
): Flow<ProfileInfo> = flow {
userService.getProfile().suspendMapSuccess {
emit(this.data.toDomain())
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun registerProfile(
nickname: String,
profileImage: String,
onError: (WithPeaceError) -> Unit,
): Flow<Unit> {
TODO("Not yet implemented")
}

override fun updateProfile(
nickname: String,
profileImage: String,
onError: suspend (WithPeaceError) -> Unit,
): Flow<Unit> = flow {
val imagePart = getImagePart(profileImage)
userService.updateProfile(
nickname.toRequestBody("text/plain".toMediaTypeOrNull()), imagePart,
).suspendMapSuccess {
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun updateNickname(nickname: String, onError: suspend (WithPeaceError) -> Unit): Flow<Unit> =
flow {
userService.updateNickname(NicknameRequest(nickname)).suspendMapSuccess {
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun updateProfileImage(
profileImage: String,
onError: suspend (WithPeaceError) -> Unit,
): Flow<Unit> = flow {
val imagePart = getImagePart(profileImage)
userService.updateImage(imagePart).suspendMapSuccess {
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun verifyNicknameDuplicated(
nickname: Nickname,
onError: suspend (WithPeaceError) -> Unit,
): Flow<Unit> = flow {
userService.isNicknameDuplicate(nickname.value).suspendMapSuccess {
if (this.data) {
onError(WithPeaceError.GeneralError(code = 2))
} else {
emit(Unit)
}
}.suspendOnError {
onError(WithPeaceError.GeneralError(statusCode.code, messageOrNull))
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun logout(onError: suspend (WithPeaceError) -> Unit): Flow<Unit> = flow {
userService.logout().suspendMapSuccess {
tokenPreferenceDataSource.removeAll()
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

private fun getImagePart(profileImage: String): MultipartBody.Part {
val requestFile: File = Uri.parse(profileImage).convertToFile(context)
val imageRequestBody = requestFile.asRequestBody("image/*".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(
"imageFile",
requestFile.name,
imageRequestBody,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class DefaultTokenPreferenceDataSource @Inject constructor(
}
}

override suspend fun removeAll() {
dataStore.edit { preferences ->
preferences.clear()
}
}

companion object {
private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN")
private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ interface TokenPreferenceDataSource {
suspend fun updateRefreshToken(refreshToken: String)

suspend fun updateAccessToken(accessToken: String)
suspend fun removeAll()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.withpeace.withpeace.core.designsystem.ui

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme

@Composable
fun TitleBar(title: String, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.height(56.dp)
.padding(start = 24.dp),
) {
Text(
text = title,
style = WithpeaceTheme.typography.title1,
color = WithpeaceTheme.colors.SystemBlack,
)
}
}
chws0508 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.withpeace.withpeace.core.designsystem.ui

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
Expand Down Expand Up @@ -41,6 +42,7 @@ fun WithPeaceBackButtonTopAppBar(
},
actions = actions,
colors = TopAppBarDefaults.topAppBarColors(containerColor = WithpeaceTheme.colors.SystemWhite),
windowInsets = WindowInsets(0, 0, 0, 0),
)
}

Expand Down
Loading