Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2910fc0
feat: datastore에 유저 아이디 필드 추가
codebidoof Feb 10, 2026
68499d8
feat: 유저 아이디 캐싱로직 구현
codebidoof Feb 10, 2026
8864921
feat: 사용자 계정 ID api 연동
codebidoof Feb 10, 2026
bdf2fa0
feat: 계정 연동 확인 콜백 인터페이스 제작
codebidoof Feb 10, 2026
b458501
refactor: 로그인&회원가입 로직 표준 ApiResponse 패턴으로 리팩토링
codebidoof Feb 10, 2026
8292fc7
feat: 연동 버튼 디자인 개선
codebidoof Feb 10, 2026
df354c5
feat: 구글 로그인 시 로그인 타입 저장 기능 추가
codebidoof Feb 10, 2026
1a12bd3
feat: 계정 연동 apiservice 작성
codebidoof Feb 10, 2026
5ad1ee9
feat: 구글 계정 연동 로직 구현
codebidoof Feb 10, 2026
20ce626
feat: 계정 연동 api 1차 연동완료. 보완 예정
codebidoof Feb 10, 2026
2eec0ea
refactor: 계정 연동 시 유저id 갱신 api 호출 강제
codebidoof Feb 10, 2026
b3e12e2
feat: 구글 계정 로그인 상태일 때 연동하기 버튼의 상태 유지 기능 추가
codebidoof Feb 10, 2026
b018a1f
feat: id토큰으로부터 이메일 파싱을 위한 싱글톤 객체 추가
codebidoof Feb 10, 2026
a3d4f0a
feat: 이메일 파싱 객체 삭제 -> 백엔드에서 필드를 받아오는 것이 아키텍쳐 상 맞음
codebidoof Feb 10, 2026
5f33fd0
feat: 변경된 api에 따른 이메일 표시 로직 구현
codebidoof Feb 11, 2026
aaf0f04
refactor: 뷰모델에서 datastore 의존성 삭제 및 그에 따른 리팩토링
codebidoof Feb 11, 2026
0e110d2
feat: 자동 로그아웃 시 데이터스토어의 모든 데이터 클리어 로직 추가
codebidoof Feb 11, 2026
b4689e0
design: 회원 탈퇴 dialog 두개 추가
codebidoof Feb 11, 2026
a0f8fcb
design: 다이알로그 디자인 보완
codebidoof Feb 11, 2026
7b95274
feat: 2번째 dialog후 로그인 화면 이동 로직 구현
codebidoof Feb 11, 2026
0e47bb2
feat: 계정 탈퇴 api 연동
codebidoof Feb 11, 2026
ca7bcb8
design: 계정 창 ui 개선
codebidoof Feb 11, 2026
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
3 changes: 2 additions & 1 deletion app/src/main/java/com/egobook/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ class MainActivity : AppCompatActivity(), BlurController, NotificationController
// 목적지 변경 리스너 추가: 특정 프래그먼트에서 바텀바 숨기기
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.diaryWriteFragment, // 일기 작성 화면
R.id.diaryWriteFragment, // 일기 작성 화면
R.id.calenderFragment, // 달력 화면
R.id.storeFragment,
R.id.accountFragment //계정 화면
-> {
binding.bottomNavigation.visibility = View.GONE
}
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/com/egobook/app/data/api/AccountApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.egobook.app.data.api

import com.egobook.app.data.model.ApiResponse
import com.egobook.app.data.model.account.AccountResponse
import com.egobook.app.data.model.account.DeleteAccountResponse
import com.egobook.app.data.model.account.LinkRequest
import com.egobook.app.data.model.account.LinkResponse
import retrofit2.http.GET
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.POST

interface AccountApiService {
//유저 id 불러오기
@GET("/home/settings")
suspend fun getUserId(): ApiResponse<AccountResponse>

@POST("/users/link/google")
suspend fun linkToGoogle(
@Body request: LinkRequest
): ApiResponse<LinkResponse>

@DELETE("/users/withdraw")
suspend fun deleteAccount(): ApiResponse<DeleteAccountResponse>

}
21 changes: 9 additions & 12 deletions app/src/main/java/com/egobook/app/data/api/AuthApiService.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.egobook.app.data.api

import com.egobook.app.data.model.ApiResponse
import com.egobook.app.data.model.auth.AccessTokenRequest
import com.egobook.app.data.model.auth.AccessTokenResponse
import com.egobook.app.data.model.auth.TokensRequestAgainByGuest
import com.egobook.app.data.model.auth.GuestTokenData
import com.egobook.app.data.model.auth.TokenData
import com.egobook.app.data.model.auth.TokenRequestByGoogle
import com.egobook.app.data.model.auth.TokenRequestByGuest
import com.egobook.app.data.model.auth.TokenResponseAgainByGuest
import com.egobook.app.data.model.auth.TokenResponseByGoogle
import com.egobook.app.data.model.auth.TokenResponseByGuest
import com.egobook.app.data.model.auth.TokensRequest
import com.egobook.app.data.model.auth.TokensResponse
import retrofit2.Response
import com.egobook.app.data.model.auth.TokensRequestAgainByGuest
import retrofit2.http.Body
import retrofit2.http.POST

Expand All @@ -20,30 +17,30 @@ interface AuthApiService {
@POST("auth/google/join")
suspend fun googleSignUp(
@Body request: TokenRequestByGoogle
): Response<TokenResponseByGoogle>
): ApiResponse<TokenData>

//Guest 최초 둘러보기
@POST("auth/guest/join")
suspend fun guestLogin(
@Body request: TokenRequestByGuest
): Response<TokenResponseByGuest>
): ApiResponse<GuestTokenData>

//액세스토큰 재발급
@POST("auth/refresh")
suspend fun getAccessToken(
@Body request: AccessTokenRequest
): Response<AccessTokenResponse>
): ApiResponse<TokenData>

//Tokens 재발급 - refreshToken까지 만료시
@POST("auth/google/recertification")
suspend fun reGetTokens(
@Body request: TokensRequest
): Response<TokensResponse>
): ApiResponse<TokenData>

//Guest로그인 상태에서 Tokens 재발급
@POST("auth/google/recertification")
suspend fun reGetTokensByGuest(
@Body request: TokensRequestAgainByGuest
): Response<TokenResponseAgainByGuest>
): ApiResponse<GuestTokenData>

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ class TokenAuthenticator @Inject constructor(
)
)

Timber.d("토큰 갱신 API 응답: 코드=${tokenResponse.code()}, 성공=${tokenResponse.isSuccessful}")
Timber.d("토큰 갱신 API 응답: 코드=${tokenResponse.code}, 메시지=${tokenResponse.message}")

if (tokenResponse.isSuccessful && tokenResponse.body() != null) {
val newAccessToken = tokenResponse.body()!!.data.accessToken
if (tokenResponse.code == "SUCCESS") {
val newAccessToken = tokenResponse.data.accessToken
val newRefreshToken = tokenResponse.data.refreshToken

// 새 액세스 토큰을 DataStore에 저장
userInfoStorage.saveAccessToken(newAccessToken)
// 새 토큰들을 DataStore에 저장
userInfoStorage.saveAllTokens(
accessToken = newAccessToken,
refreshToken = newRefreshToken
)

Timber.d("액세스 토큰 갱신 성공")

Expand All @@ -78,7 +82,7 @@ class TokenAuthenticator @Inject constructor(

} else {
// 리프레시 토큰 갱신 실패 -> 리프레시 토큰 만료로 판단
Timber.e("리프레시 토큰 갱신 실패: ${tokenResponse.code()}, 로그아웃 처리")
Timber.e("리프레시 토큰 갱신 실패: ${tokenResponse.code}, 로그아웃 처리")
handleLogout()
null
}
Expand All @@ -91,17 +95,20 @@ class TokenAuthenticator @Inject constructor(
}

/**
* 로그아웃 처리: 로그인 화면으로 이동
* 로그아웃 처리: 토큰 클리어 후 로그인 화면으로 이동
*/
private fun handleLogout() {
Timber.d("로그아웃 처리 시작")

// 로그인 화면으로 이동

runBlocking {
userInfoStorage.clearAll() // 토큰, id, 이메일 등등 싹다 삭제
}

val intent = Intent(context, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
context.startActivity(intent)

Timber.d("로그인 화면으로 이동")
}

Expand Down
47 changes: 42 additions & 5 deletions app/src/main/java/com/egobook/app/data/local/UserInfoStorage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.egobook.app.domain.model.User
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
Expand All @@ -23,7 +22,6 @@ class UserInfoStorage @Inject constructor(
) {
private val dataStore = context.dataStore


/**
* LoginType 저장
*/
Expand All @@ -48,6 +46,43 @@ class UserInfoStorage @Inject constructor(
}
}

/**
* USER ID 저장
*/
suspend fun saveUserId(id: String) {
dataStore.edit { preferences ->
preferences[USER_ID] = id
}
}

/**
* USER ID 읽기
*/

fun getUserId(): Flow<String?> {
return dataStore.data.map { preferences ->
preferences[USER_ID]
}
}

/**
* USER EMAIL 저장
*/
suspend fun saveUserEmail(email: String) {
dataStore.edit { preferences ->
preferences[USER_EMAIL] = email
}
}

/**
* USER EMAIL 읽기
*/
fun getUserEmail(): Flow<String?> {
return dataStore.data.map { preferences ->
preferences[USER_EMAIL]
}
}

/**
* Access Token 저장
*/
Expand Down Expand Up @@ -126,7 +161,6 @@ class UserInfoStorage @Inject constructor(
suspend fun saveAllTokens(
accessToken: String,
refreshToken: String,
idToken: String? = null,
recoverToken: String? = null,
) {
dataStore.edit { preferences ->
Expand All @@ -137,14 +171,15 @@ class UserInfoStorage @Inject constructor(
}

/**
* 모든 데이터 삭제 (로그아웃 시 사용)
* 모든 데이터 삭제 (로그아웃 및 회원탈퇴 시 사용)
*/
suspend fun clearAll() {
dataStore.edit { preferences ->
preferences.clear()
}
}

// 로그인 타입 정의
enum class LoginType {
GOOGLE, GUEST
}
Expand All @@ -153,11 +188,13 @@ class UserInfoStorage @Inject constructor(
companion object {

private val LOGIN_TYPE = stringPreferencesKey("login_type")
private val USER_ID = stringPreferencesKey("user_id")

private val USER_EMAIL = stringPreferencesKey("email")
private val KEY_ACCESS_TOKEN = stringPreferencesKey("access_token")
private val KEY_REFRESH_TOKEN = stringPreferencesKey("refresh_token")
private val KEY_RECOVER_TOKEN = stringPreferencesKey("recover_token")
private val KEY_DEVICE_UID = stringPreferencesKey("device_uid")


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.egobook.app.data.model.account

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class AccountResponse(
@SerialName("accountCode")
val accountCode: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.egobook.app.data.model.account

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class DeleteAccountResponse(
@SerialName("data")
val data: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.egobook.app.data.model.account

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LinkRequest(
@SerialName("idToken")
val idToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.egobook.app.data.model.account

import kotlinx.serialization.SerialName

data class LinkResponse(
@SerialName("accessToken")
val accessToken: String,

@SerialName("refreshToken")
val refreshToken: String,

@SerialName("email")
val email: String,
)
Loading