Skip to content

Commit

Permalink
feat/#52 signup 기능을 구현한다 (#74)
Browse files Browse the repository at this point in the history
* Rebase develop

* feat: basicTextField 중앙 정렬 적용

* feat: role에 대한 회원가입 화면 이동로직 추가

* feat: 최초 로그인 profile 수정

* refactor: ProfileEditor, NickNameEditor 공용함수화

* fix: 토큰 저장방식 수정

* feat: 최초로그인 구현

* fix: signUp 버튼 클릭시 이벤트 변경

* feat: role, id 로컬 저장 및 따라 자동로그인 로직 추가

* feat: 회원가입 성공시 권한 업그레이드
  • Loading branch information
rhkrwngud445 authored Apr 3, 2024
1 parent b7087bb commit 7ebc012
Show file tree
Hide file tree
Showing 58 changed files with 938 additions and 307 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ android {
dependencies {
implementation(libs.androidx.core.splashscreen)
implementation(project(":feature:login"))
implementation(project(":feature:signup"))
implementation(project(":feature:home"))
implementation(project(":feature:postlist"))
implementation(project(":feature:mypage"))
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme
import com.withpeace.withpeace.navigation.WithpeaceNavHost
import kotlinx.coroutines.launch


@Composable
fun WithpeaceApp(
startDestination: String,
Expand Down
30 changes: 28 additions & 2 deletions app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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.home.navigation.navigateHome
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
Expand All @@ -19,6 +20,8 @@ import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph
import com.withpeace.withpeace.feature.postlist.navigation.postListGraph
import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT
import com.withpeace.withpeace.feature.registerpost.navigation.registerPostNavGraph
import com.withpeace.withpeace.feature.signup.navigation.navigateSignUp
import com.withpeace.withpeace.feature.signup.navigation.signUpNavGraph

@Composable
fun WithpeaceNavHost(
Expand All @@ -32,7 +35,30 @@ fun WithpeaceNavHost(
navController = navController,
startDestination = startDestination,
) {
loginNavGraph(onShowSnackBar = onShowSnackBar)
loginNavGraph(
onShowSnackBar = onShowSnackBar,
onSignUpNeeded = {
navController.navigateSignUp()
},
onLoginSuccess = {
navController.navigateHome()
},
)
signUpNavGraph(
onShowSnackBar = onShowSnackBar,
onNavigateToGallery = {
navController.navigateToGallery(imageLimit = 1)
},
onSignUpSuccess = {
navController.navigateHome(
navOptions = navOptions {
popUpTo(navController.graph.id) {
inclusive = true
}
},
)
},
)
registerPostNavGraph(
onShowSnackBar = onShowSnackBar,
onCompleteRegisterPost = {},
Expand Down Expand Up @@ -97,4 +123,4 @@ fun WithpeaceNavHost(
)
postListGraph(onShowSnackBar)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.withpeace.withpeace.core.data.mapper

import com.withpeace.withpeace.core.domain.model.role.Role

internal fun String.roleToDomain(): Role {
return when (this) {
"GUEST" -> Role.GUEST
"USER" -> Role.USER
else -> Role.UNKNOWN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.withpeace.withpeace.core.data.repository
import com.skydoves.sandwich.message
import com.skydoves.sandwich.suspendMapSuccess
import com.skydoves.sandwich.suspendOnFailure
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.data.mapper.roleToDomain
import com.withpeace.withpeace.core.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.role.Role
import com.withpeace.withpeace.core.domain.repository.TokenRepository
import com.withpeace.withpeace.core.network.di.request.SignUpRequest
import com.withpeace.withpeace.core.network.di.response.LoginResponse
import com.withpeace.withpeace.core.network.di.service.AuthService
import com.withpeace.withpeace.core.network.di.service.UserService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
Expand All @@ -17,40 +19,33 @@ import javax.inject.Inject

class DefaultTokenRepository @Inject constructor(
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
private val userPreferenceDataSource: UserPreferenceDataSource,
private val authService: AuthService,
) : TokenRepository {
override suspend fun isLogin(): Boolean {
val token = tokenPreferenceDataSource.accessToken.firstOrNull()
return token != null
val userRole = userPreferenceDataSource.userRole.firstOrNull()
return token != null && userRole?.roleToDomain() == Role.USER //TODO("토큰이 만료되었는지 확인 필요")
}

override suspend fun signUp(
email: String,
nickname: String,
onError: (String) -> Unit,
): Flow<Unit> = flow {
authService.signUp(
SignUpRequest(email = email, nickname = nickname, deviceToken = null),
).suspendMapSuccess {
val data = this.data
tokenPreferenceDataSource.updateAccessToken(data.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.refreshToken)
emit(Unit)
}.suspendOnFailure { onError(message()) }
}.flowOn(Dispatchers.IO)

override fun getTokenByGoogle(
idToken: String,
onError: (String) -> Unit,
): Flow<Unit> = flow {
): Flow<Role> = flow {
authService.googleLogin(AUTHORIZATION_FORMAT.format(idToken)).suspendMapSuccess {
val data = this.data
tokenPreferenceDataSource.updateAccessToken(data.tokenResponse.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.tokenResponse.refreshToken)
emit(Unit)
saveLocalLoginInfo(data)
emit(data.role.roleToDomain())
}.suspendOnFailure { onError(message()) }
}.flowOn(Dispatchers.IO)

private suspend fun saveLocalLoginInfo(data: LoginResponse) {
tokenPreferenceDataSource.updateAccessToken(data.tokenResponse.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.tokenResponse.refreshToken)
userPreferenceDataSource.updateUserId(data.userId)
userPreferenceDataSource.updateUserRole(data.role)
}

companion object {
private const val AUTHORIZATION_FORMAT = "Bearer %s"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ 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.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.SignUpInfo
import com.withpeace.withpeace.core.domain.model.WithPeaceError
import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile
import com.withpeace.withpeace.core.domain.model.profile.Nickname
import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo
import com.withpeace.withpeace.core.domain.model.role.Role
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
Expand All @@ -31,6 +34,7 @@ class DefaultUserRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val userService: UserService,
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
private val userPreferenceDataSource: UserPreferenceDataSource,
) : UserRepository {
override fun getProfile(
onError: suspend (WithPeaceError) -> Unit,
Expand All @@ -49,12 +53,38 @@ class DefaultUserRepository @Inject constructor(
}
}

override fun registerProfile(
nickname: String,
profileImage: String,
onError: (WithPeaceError) -> Unit,
): Flow<Unit> {
TODO("Not yet implemented")
override suspend fun signUp(
signUpInfo: SignUpInfo,
onError: suspend (WithPeaceError) -> Unit,
): Flow<Unit> = flow {
val nicknameRequestBody =
signUpInfo.nickname.toRequestBody("text/plain".toMediaTypeOrNull())
val request =
if (signUpInfo.profileImage.isNullOrEmpty()) {
userService.signUp(
nicknameRequestBody,
)
} else {
val profileImagePart = getImagePart(signUpInfo.profileImage!!)
userService.signUp(nicknameRequestBody, profileImagePart)
}

request.suspendMapSuccess {
val data = this.data
userPreferenceDataSource.updateUserRole(Role.USER.name)
tokenPreferenceDataSource.updateAccessToken(data.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.refreshToken)
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 updateProfile(
Expand Down Expand Up @@ -134,9 +164,12 @@ class DefaultUserRepository @Inject constructor(
}
}



override fun logout(onError: suspend (WithPeaceError) -> Unit): Flow<Unit> = flow {
userService.logout().suspendMapSuccess {
tokenPreferenceDataSource.removeAll()
userPreferenceDataSource.removeAll()
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.withpeace.withpeace.core.datastore.dataStore
package com.withpeace.withpeace.core.datastore.dataStore.token

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.withpeace.withpeace.core.datastore.dataStore
package com.withpeace.withpeace.core.datastore.dataStore.token

import kotlinx.coroutines.flow.Flow

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.withpeace.withpeace.core.datastore.dataStore.user

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Named

class DefaultUserPreferenceDataSource @Inject constructor(
@Named("user") private val dataStore: DataStore<Preferences>,
) : UserPreferenceDataSource {
override val userId: Flow<Long?> = dataStore.data.map { preferences ->
preferences[USER_ID]
}
override val userRole: Flow<String?> = dataStore.data.map { preferences ->
preferences[USER_ROLE]
}

override suspend fun updateUserId(userId: Long) {
dataStore.edit { preferences ->
preferences[USER_ID] = userId
}
}

override suspend fun updateUserRole(userRole: String) {
dataStore.edit { preferences ->
preferences[USER_ROLE] = userRole
}
}

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

companion object {
private val USER_ID = longPreferencesKey("USER_ID")
private val USER_ROLE = stringPreferencesKey("USER_ROLE")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.withpeace.withpeace.core.datastore.dataStore.user

import kotlinx.coroutines.flow.Flow

interface UserPreferenceDataSource {

val userId: Flow<Long?>

val userRole: Flow<String?>

suspend fun updateUserId(userId: Long)

suspend fun updateUserRole(userRole: String)
suspend fun removeAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ import javax.inject.Singleton
object DataStoreModule {

private const val AUTH_DATASTORE_NAME = "AUTH_PREFERENCES"
private const val USER_DATASTORE_NAME = "USER_PREFERENCES"

private val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = AUTH_DATASTORE_NAME)
private val Context.userDataStore: DataStore<Preferences> by preferencesDataStore(name = USER_DATASTORE_NAME)

@Provides
@Singleton
@Named("auth")
fun providesTokenDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.authDataStore

@Provides
@Singleton
@Named("user")
fun providesUserDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.userDataStore
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.withpeace.withpeace.core.datastore.di

import com.withpeace.withpeace.core.datastore.dataStore.DefaultTokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.token.DefaultTokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.DefaultUserPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -17,4 +19,10 @@ interface PreferenceDataSourceModule {
fun bindsTokenPreferenceDataSource(
defaultTokenPreferenceDataSource: DefaultTokenPreferenceDataSource,
): TokenPreferenceDataSource

@Binds
@Singleton
fun bindsUserPreferenceDataSource(
defaultUserPreferenceDataSource: DefaultUserPreferenceDataSource,
): UserPreferenceDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.withpeace.withpeace.core.domain.model

data class SignUpInfo(
val nickname: String,
val profileImage: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.withpeace.withpeace.core.domain.model.role

enum class Role {
GUEST, USER, UNKNOWN
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package com.withpeace.withpeace.core.domain.repository

import com.withpeace.withpeace.core.domain.model.role.Role
import kotlinx.coroutines.flow.Flow

interface TokenRepository {

suspend fun isLogin(): Boolean

suspend fun signUp(
email: String,
nickname: String,
onError: (String) -> Unit,
): Flow<Unit>

fun getTokenByGoogle(
idToken: String,
onError: (String) -> Unit,
): Flow<Unit>
): Flow<Role>
}
Loading

0 comments on commit 7ebc012

Please sign in to comment.