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

[UI/#159][FEAT/#160] mypage / 전체 로직 구현 #188

Merged
merged 36 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f0db544
chore/#159 : coil 의존성 추가
shinythinking Nov 30, 2024
0ad39a5
ui/#159 : MypageScreen ui 구현
shinythinking Nov 30, 2024
e43939e
ui/#159 : ProfileEditScreen ui 구현
shinythinking Nov 30, 2024
6f7b50b
feat/#160 : MypageRoute 설정
shinythinking Nov 30, 2024
7f683a4
feat/#160 : Mypage navGraph 설정 및 navigation 함수 구현
shinythinking Nov 30, 2024
7091184
feat/#160 : NavHost에 Mypage navGraph 추가
shinythinking Nov 30, 2024
2b33c6d
feat/#160 : navigateToProfileEdit 구현
shinythinking Nov 30, 2024
915f963
feat/#160 : screen에 navigation 관련 로직 추가
shinythinking Nov 30, 2024
58a1956
chore/#160 : 브라우저 여는 의존성 추가
shinythinking Dec 2, 2024
fd1e327
feat/#160 : firestore의 name과 profileurl 초기화 함수 추가
shinythinking Dec 2, 2024
24b0aca
feat/#160 : GoogleOauth에 로그아웃 및 회원탈퇴 함수 추가
shinythinking Dec 2, 2024
ecdcd2b
fix/#160 : conflict 대응
shinythinking Dec 2, 2024
0b8842b
feat/#160 : mvi 기본 설정 (State, Intent, SideEffect)
shinythinking Dec 2, 2024
03ce36e
chore/#160 : 스트링 리소스 추가
shinythinking Dec 2, 2024
041dd79
feat/#160 : 외부 브라우저 연결을 위한 manifest 추가
shinythinking Dec 2, 2024
6219ded
feat/#160 : MypageViewModel 구현
shinythinking Dec 2, 2024
0e4b5c0
feat/#160 : ProfileEditViewModel 구현
shinythinking Dec 2, 2024
5e57297
feat/#160 : MypageScreen 구현
shinythinking Dec 2, 2024
9db6086
feat/#160 : ProfileEditScreen 구현
shinythinking Dec 2, 2024
914f041
fix/#160 : ktlint 대응 1
shinythinking Dec 2, 2024
ed674c4
fix/#160 : ktlint 대응 2
shinythinking Dec 2, 2024
24e5703
fix/#160 : ktlint 대응 3
shinythinking Dec 2, 2024
9435ef6
fix/#160 : ktlint 대응 4
shinythinking Dec 2, 2024
b0cf29f
fix/#160 : ktlint 대응 5
shinythinking Dec 2, 2024
bd73f98
chore/#160 : 의존성 추가
shinythinking Dec 2, 2024
d7d51fd
fix/#160 : ktlint 대응
shinythinking Dec 2, 2024
3a0fc7d
fix/#160 : 에러 대응 추가
shinythinking Dec 2, 2024
79c32aa
chore/#160 : 불필요한 manifest 파일 제거
shinythinking Dec 2, 2024
e1ba04e
chore/#160 : 스트링 리소스에 추가
shinythinking Dec 2, 2024
843ca46
feat/#160 : 프로필 로딩 예외처리 추가
shinythinking Dec 2, 2024
1c61c80
chore/#160 : 디렉토리 분리
shinythinking Dec 2, 2024
261633a
fix/#160 : 이용약관으로 이동하는 것을 sideEffect로 처리
shinythinking Dec 2, 2024
d9b89f6
chore/#160 : buildconfig에 링크 추가
shinythinking Dec 2, 2024
c26a3a0
feat/#160 : 로그아웃시, 백스택을 제거한 뒤, 로그인 화면으로
shinythinking Dec 2, 2024
0e0af95
feat/#160 : pr_checker에 privacypolicy 관련 코드 추가
shinythinking Dec 2, 2024
5e69823
refactor/#160 : 불필요한 sideEffect 삭제
shinythinking Dec 2, 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
2 changes: 2 additions & 0 deletions .github/workflows/pr_checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ jobs:
NAVER_MAP_CLIENT_ID: ${{ secrets.NAVER_MAP_CLIENT_ID }}
NAVER_MAP_CLIENT_SECRET: ${{ secrets.NAVER_MAP_CLIENT_SECRET }}
GOOGLE_WEB_CLIENT_ID: ${{ secrets.GOOGLE_WEB_CLIENT_ID }}
PRIVACY_POLICY: ${{ secrets.PRIVACY_POLICY }}
run: |
echo NAVER_MAP_CLIENT_ID=$NAVER_MAP_CLIENT_ID > ./local.properties
echo NAVER_MAP_CLIENT_SECRET=$NAVER_MAP_CLIENT_SECRET >> ./local.properties
echo GOOGLE_WEB_CLIENT_ID=$GOOGLE_WEB_CLIENT_ID >> ./local.properties
echo PRIVACY_POLICY=$PRIVACY_POLICY >> ./local.properties
Comment on lines +41 to +46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local properties 추가 하셨나보네요!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 ㅎㅎ
태희님의 추천으로 local properties에 추가했습니다.


- name: Create google-services.json
run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.exceptions.GetCredentialCancellationException
import com.boostcamp.mapisode.model.AuthData
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.firebase.auth.AuthResult
Expand Down Expand Up @@ -65,17 +66,42 @@ class GoogleOauth(private val context: Context) {
}
}

suspend fun googleSignOut() = FirebaseAuth.getInstance().signOut()

suspend fun isUserLoggedIn(): Boolean = FirebaseAuth.getInstance().currentUser != null

suspend fun deleteCurrentUser() {
try {
val credentialManager = initializeCredentialManager()
val googleIdOption = GetGoogleIdOption.Builder().setFilterByAuthorizedAccounts(true)
.setNonce(generateNonce()).build()
val request: GetCredentialRequest =
GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build()

val resultCredential = credentialManager.getCredential(context, request).credential
val validatedCredential = validateCredential(resultCredential)

val authCredential = GoogleAuthProvider.getCredential(validatedCredential.idToken, null)

FirebaseAuth.getInstance().currentUser?.let { user ->
user.reauthenticate(authCredential).await()
user.delete().await()
} ?: throw Exception("현재 로그인된 사용자가 없습니다.")
} catch (e: Exception) {
throw Exception("계정 삭제 실패: ${e.message}")
}
}

private fun initializeCredentialManager(): CredentialManager = CredentialManager.create(context)

private fun createGoogleSignInOption(): GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(BuildConfig.GOOGLE_WEB_CLIENT_ID)
.setNonce(generateNonce())
.build()
private fun createGoogleSignInOption(): GetSignInWithGoogleOption =
GetSignInWithGoogleOption.Builder(BuildConfig.GOOGLE_WEB_CLIENT_ID)
.setNonce(generateNonce()).build()

private fun createCredentialRequest(googleIdOption: GetSignInWithGoogleOption):
GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
GetCredentialRequest = GetCredentialRequest
.Builder()
.addCredentialOption(googleIdOption).build()

private fun validateCredential(credential: Credential): GoogleIdTokenCredential {
if (credential is CustomCredential &&
Expand All @@ -91,8 +117,7 @@ class GoogleOauth(private val context: Context) {
firebaseAuth: FirebaseAuth,
googleIdTokenCredential: GoogleIdTokenCredential,
): AuthResult {
val authCredential =
GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
val authCredential = GoogleAuthProvider.getCredential(googleIdTokenCredential.idToken, null)
return firebaseAuth.signInWithCredential(authCredential).await()
}

Expand Down
12 changes: 12 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_document.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="@android:color/white"
android:pathData="M7,3H4v3H2V1h5V3zM22,6V1h-5v2h3v3H22zM7,21H4v-3H2v5h5V21zM20,18v3h-3v2h5v-5H20zM17,6H7v12h10V6zM19,18c0,1.1 -0.9,2 -2,2H7c-1.1,0 -2,-0.9 -2,-2V6c0,-1.1 0.9,-2 2,-2h10c1.1,0 2,0.9 2,2V18zM15,8H9v2h6V8zM15,11H9v2h6V11zM15,14H9v2h6V14z" />

</vector>
16 changes: 16 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_withdrawal.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="@android:color/white"
android:pathData="M15.18,10.94c0.2,-0.44 0.32,-0.92 0.32,-1.44C15.5,7.57 13.93,6 12,6c-0.52,0 -1,0.12 -1.44,0.32L15.18,10.94z" />

<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-1.85 0.63,-3.55 1.69,-4.9l2.86,2.86c0.21,1.56 1.43,2.79 2.99,2.99l2.2,2.2C13.17,15.05 12.59,15 12,15c-2.32,0 -4.45,0.8 -6.14,2.12C4.7,15.73 4,13.95 4,12zM12,20c-1.74,0 -3.34,-0.56 -4.65,-1.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20zM18.31,16.9L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8C20,13.85 19.37,15.54 18.31,16.9z" />

</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.boostcamp.mapisode.navigation

import kotlinx.serialization.Serializable

sealed interface MypageRoute : Route {
@Serializable
data object ProfileEdit : MypageRoute
}
Comment on lines +5 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별도로 Route 정의한게 이미 있습니다. 왜 만드신거죠?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로필을 변경하는 화면으로 가는 route가 필요해서 뚫었습니다. 다른 좋은 방법이 있나요???

Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,23 @@ class UserRepositoryImpl @Inject constructor(database: FirebaseFirestore) : User
throw Exception("Failed to get user", e)
}
}

override suspend fun updateUserNameAndProfileUrl(
uid: String,
userName: String,
profileUrl: String,
) {
val userDocument = userCollection.document(uid)

try {
userDocument.update(
mapOf(
"name" to userName,
"profileUrl" to profileUrl,
),
).await()
} catch (e: Exception) {
throw Exception("Failed to update user", e)
}
}
Comment on lines +59 to +76
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기랑 createUser 함수 다시보니까 firebase storage에 저장/덮어씌우기 하는 코드가 안보이네요.
로컬에서의 url은 디바이스에 저장된 경로고, 서버에 올라가야할 url은 firebase storage이에요.
coil에서는 두가지 전부 지원을 해서 get/create 에서 하나의 url 프로퍼티를 공용하고 있는데 update같은 경우에는 프로퍼티가 두가지여야 해요.
업데이트에 한해서는 서버 url, 로컬url 두가지 프로퍼티를 갖고 대조해서 바뀐 이미지인지 아닌지 구분하고 바뀌었다면 storage에 업로드 후 user collection에 저장해야 해요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 ㅠㅠ
제게 필요한 부분이었습니다!
코드를 짜다보니 꽤 길어지고, 리뷰를 받아야 할 부분이 있다고 판단하여 다른 pr로 올리겠습니다!

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface UserRepository {

suspend fun getUserInfo(uid: String): UserModel
suspend fun isUserExist(uid: String): Boolean

suspend fun updateUserNameAndProfileUrl(uid: String, userName: String, profileUrl: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import com.boostcamp.mapisode.auth.GoogleOauth
import com.boostcamp.mapisode.ui.base.UiIntent

sealed interface AuthIntent : UiIntent {
data object Init : AuthIntent
data class OnGoogleSignInClick(val googleOauth: GoogleOauth) : AuthIntent
data class OnNicknameChange(val nickname: String) : AuthIntent
data class OnProfileUrlchange(val profileUrl: String) : AuthIntent
data object OnSignUpClick : AuthIntent
data object OnAutoLogin : AuthIntent
data object OnLoginSuccess : AuthIntent
data object OnBackClickedInSignUp : AuthIntent
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fun AuthRoute(
val googleOauth = GoogleOauth(context)

LaunchedEffect(Unit) {
viewModel.onIntent(AuthIntent.OnAutoLogin)
viewModel.onIntent(AuthIntent.Init)
}

LaunchedEffect(Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ class AuthViewModel @Inject constructor(

override fun onIntent(intent: AuthIntent) {
when (intent) {
is AuthIntent.Init -> onInit()
is AuthIntent.OnGoogleSignInClick -> handleGoogleSignIn(intent.googleOauth)
is AuthIntent.OnNicknameChange -> onNicknameChange(intent.nickname)
is AuthIntent.OnProfileUrlchange -> onProfileUrlChange(intent.profileUrl)
is AuthIntent.OnSignUpClick -> handleSignUp()
is AuthIntent.OnAutoLogin -> handleAutoLogin()
is AuthIntent.OnLoginSuccess -> handleLoginSuccess()
is AuthIntent.OnBackClickedInSignUp -> onBackClickedInSignUp()
}
}

private fun onInit() {
handleAutoLogin()
}
Comment on lines +37 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수를 호출하는 함수? 필요없지 않나요..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러게요 ㅋㅋㅋㅋㅋ
원래 저기서 intent를 호출하는 로직이 있었는데 불필요해서 삭제했습니다.
onInit은 아무리 봐도 쓸모가 없네요 ㅋㅋㅋ
감사합니다!!!!!!!!!!!!!!!!!!!🤩


private fun onBackClickedInSignUp() {
intent {
copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.boostcamp.mapisode.mygroup.navigation.navigateGroupCreation
import com.boostcamp.mapisode.mygroup.navigation.navigateGroupDetail
import com.boostcamp.mapisode.mygroup.navigation.navigateGroupEdit
import com.boostcamp.mapisode.mygroup.navigation.navigateGroupJoin
import com.boostcamp.mapisode.mypage.navigation.navigateToProfileEdit
import com.boostcamp.mapisode.navigation.MainRoute
import com.boostcamp.mapisode.navigation.Route

Expand Down Expand Up @@ -62,6 +63,12 @@ internal class MainNavigator(
}
}

fun navigateToLogin() {
navController.navigate(startDestination) {
popUpTo(startDestination) { inclusive = true }
}
}

fun navigateToMain() {
navController.navigate(MainRoute.Home)
}
Expand Down Expand Up @@ -113,6 +120,10 @@ internal class MainNavigator(
navController.navigateGroupEdit(groupId)
}

fun navigateToProfileEdit() {
navController.navigateToProfileEdit()
}

private fun popBackStack() {
navController.popBackStack()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ internal fun MainNavHost(
},
onEpisodeClick = navigator::navigateToEpisodeDetail,
)
addMyPageNavGraph()
addMyPageNavGraph(
onBackClick = navigator::popBackStackIfNotHome,
onProfileEditClick = navigator::navigateToProfileEdit,
onNavgiatetoLogin = navigator::navigateToLogin,
)
}
}
}
22 changes: 22 additions & 0 deletions feature/mypage/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
alias(libs.plugins.mapisode.feature)
}

android {
namespace = "com.boostcamp.mapisode.mypage"

defaultConfig {
val privacyPolicy =
gradleLocalProperties(rootDir, providers).getProperty("PRIVACY_POLICY") ?: ""
if (privacyPolicy.isEmpty()) {
throw GradleException("PRIVACY_POLICY is not set.")
}
buildConfigField("String", "PRIVACY_POLICY", "\"$privacyPolicy\"")
}

buildFeatures {
buildConfig = true
}
}

dependencies {
implementation(libs.bundles.coil)
implementation(libs.androidx.browser)
implementation(projects.core.auth)
implementation(projects.domain.user)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.boostcamp.mapisode.mypage.intent

import android.content.Context
import com.boostcamp.mapisode.auth.GoogleOauth
import com.boostcamp.mapisode.ui.base.UiIntent

sealed interface MypageIntent : UiIntent {
data object Init : MypageIntent
data class LogoutClick(val googleOauth: GoogleOauth) : MypageIntent
data object ProfileEditClick : MypageIntent
data class PrivacyPolicyClick(val context: Context, val useCustomTab: Boolean) : MypageIntent
data object WithdrawalClick : MypageIntent
data class ConfirmClick(val googleOauth: GoogleOauth) : MypageIntent
data object TurnOffDialog : MypageIntent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.boostcamp.mapisode.mypage.intent

import com.boostcamp.mapisode.ui.base.UiIntent

sealed interface ProfileEditIntent : UiIntent {
data object Init : ProfileEditIntent
data class NameChanged(val nickname: String) : ProfileEditIntent
data class ProfileChanged(val url: String) : ProfileEditIntent
data object PhotopickerClick : ProfileEditIntent
data object EditClick : ProfileEditIntent
data object BackClick : ProfileEditIntent
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,38 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.boostcamp.mapisode.mypage.MyPageRoute
import com.boostcamp.mapisode.mypage.screen.MypageRoute
import com.boostcamp.mapisode.mypage.screen.ProfileEditRoute
import com.boostcamp.mapisode.navigation.MainRoute
import com.boostcamp.mapisode.navigation.MypageRoute

fun NavController.navigateMyPage(
navOptions: NavOptions? = null,
) {
navigate(MainRoute.Mypage, navOptions)
}

fun NavGraphBuilder.addMyPageNavGraph() {
fun NavController.navigateToProfileEdit(
navOptions: NavOptions? = null,
) {
navigate(MypageRoute.ProfileEdit, navOptions)
}

fun NavGraphBuilder.addMyPageNavGraph(
onBackClick: () -> Unit,
onProfileEditClick: () -> Unit,
onNavgiatetoLogin: () -> Unit,
) {
composable<MainRoute.Mypage> {
MyPageRoute()
MypageRoute(
onProfileEditClick = onProfileEditClick,
onNavigateToLogin = onNavgiatetoLogin,
)
}

composable<MypageRoute.ProfileEdit> {
ProfileEditRoute(
onBackClick = onBackClick,
)
}
}
Loading
Loading