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

[Fix/#871] 마이페이지 오류 수정 #907

Merged
merged 31 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b91142f
[fix/#871] remove logout responses
leeeyubin Sep 26, 2024
661e1b5
[fix/#871] delete LogOutResponse.kt
leeeyubin Sep 26, 2024
44b6727
[feature/#871] pull from develop
leeeyubin Sep 30, 2024
ec9c02d
[feature/#871] get Profile Message
leeeyubin Sep 30, 2024
749226f
[feature/#871] modify button enable condition
leeeyubin Sep 30, 2024
f6a3495
[feature/#871] modify button enable condition
leeeyubin Sep 30, 2024
9d9b32c
[feature/#871] git pull from develop
leeeyubin Oct 5, 2024
6270326
[feature/#871] git pull from develop
leeeyubin Oct 5, 2024
9eec6da
[feature/#871] make adjust sentence toast
leeeyubin Oct 6, 2024
70f077e
[feature/#871] delete mypage nickname view
leeeyubin Oct 7, 2024
eb25798
[feature/#871] make resetSoptamp success
leeeyubin Oct 10, 2024
7194eff
[feature/#871] add refreshToken empty function
leeeyubin Oct 10, 2024
2afdbfc
[feature/#871] modify login and logout logic
leeeyubin Oct 10, 2024
a994e4b
[feature/#871] change mypage state
leeeyubin Oct 12, 2024
880fb19
[feature/#871] delete delay function
leeeyubin Oct 12, 2024
eea9cc4
[feature/#871] git pull from develop
leeeyubin Oct 12, 2024
adf5d5b
[feature/#871] code refactoring
leeeyubin Oct 13, 2024
fdfaf0b
[feature/#871] code refactoring
leeeyubin Oct 13, 2024
b12835f
[feature/#871] git pull from develop
leeeyubin Oct 13, 2024
9b6b873
[feature/#871] MIT License
leeeyubin Oct 13, 2024
c487625
[feature/#871] change ui state
leeeyubin Oct 14, 2024
7746918
[feature/#871] rename onDismiss function
leeeyubin Oct 14, 2024
2f50399
[feature/#871] update 함수 사용
leeeyubin Oct 14, 2024
8dc7cac
[feature/#871] myPageState 변수명 수정
leeeyubin Oct 14, 2024
8bac4ac
[feature/#871] sealed class -> sealed interface
leeeyubin Oct 14, 2024
abd79f9
[feature/#871] 초기 한마디 변수명 수정
leeeyubin Oct 14, 2024
f8d30f3
[feature/#871] git pull from develop
leeeyubin Oct 15, 2024
482e355
[feature/#871] git pull from develop
leeeyubin Oct 17, 2024
8fd8527
[feature/#871] logout -> deleteUserInfo
leeeyubin Oct 17, 2024
6083cb9
[feature/#871] 타입 명시
leeeyubin Oct 17, 2024
e22c296
[feature/#871] License
leeeyubin Oct 17, 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
4 changes: 0 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@
android:name=".feature.mypage.signOut.SignOutActivity"
android:exported="false"
android:theme="@style/Theme.SOPT" />
<activity
android:name=".feature.mypage.soptamp.nickName.ChangeNickNameActivity"
android:exported="false"
android:theme="@style/Theme.SOPT" />
<activity
android:name=".feature.mypage.soptamp.sentence.AdjustSentenceActivity"
android:exported="false"
Expand Down
983 changes: 24 additions & 959 deletions app/src/release/generated/baselineProfiles/baseline-prof.txt

Large diffs are not rendered by default.

983 changes: 24 additions & 959 deletions app/src/release/generated/baselineProfiles/startup-prof.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ package org.sopt.official.auth.impl.api

import org.sopt.official.auth.impl.model.request.AuthRequest
import org.sopt.official.auth.impl.model.response.LogOutRequest
import org.sopt.official.auth.impl.model.response.LogOutResponse
import org.sopt.official.network.model.response.AuthResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
Expand All @@ -41,5 +40,5 @@ interface AuthService {
suspend fun withdraw()

@HTTP(method = "DELETE", path = "user/logout", hasBody = true)
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 그냥 궁금한건데 Delete 어노테이션과 차이가 있나요?

Copy link
Member

Choose a reason for hiding this comment

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

Delete에 body 값이 안들어가는게 원칙인데 굳이 넣어줘야할 때에는 이렇게 사용하는걸로 알고 있음

Copy link
Member

@l2hyunwoo l2hyunwoo Oct 13, 2024

Choose a reason for hiding this comment

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

저거 무시하고 그냥 body 패러미터 넣으면 에러 일으킴ㅋㅋㅋ

그래서 원칙적으로는

  1. 서버한테 바디 패러미터 빼달라고 하거나
  2. 저렇게 사용하거나

인데 난 갠적으로 1로 쇼부치는게 더 깔끔하다고 생각하긴함. (정 안되면 2번)

Copy link
Contributor

@s9hn s9hn Oct 13, 2024

Choose a reason for hiding this comment

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

오.. 신기하네 하나 알아갑니다. 저도 같은이유라면 서버에게 요청하고 안되면 뭐 2번해야쥬

suspend fun logOut(@Body body: LogOutRequest): LogOutResponse
suspend fun logOut(@Body body: LogOutRequest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ package org.sopt.official.auth.impl.remote
import javax.inject.Inject
import org.sopt.official.auth.impl.api.AuthService
import org.sopt.official.auth.impl.model.response.LogOutRequest
import org.sopt.official.auth.impl.model.response.LogOutResponse
import org.sopt.official.auth.impl.source.RemoteAuthDataSource
import org.sopt.official.common.di.Auth
import org.sopt.official.network.model.request.RefreshRequest
Expand All @@ -46,7 +45,7 @@ class DefaultRemoteAuthDataSource @Inject constructor(
authService.withdraw()
}

override suspend fun logout(request: LogOutRequest): LogOutResponse {
return authService.logOut(request)
override suspend fun logout(request: LogOutRequest) {
Copy link
Contributor

Choose a reason for hiding this comment

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

개인적으로 data 레이어에서 로그인 혹은 로그아웃이라는 함수 네이밍을 지양하는 편입니다! 저장소 역할을 담당하는 객체가 로그아웃이라는 책임이 있다고 생각하지 않아요!

Copy link
Member

Choose a reason for hiding this comment

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

@s9hn 그러면 함수 네이밍 추천좀...

Copy link
Contributor

Choose a reason for hiding this comment

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

ㅋ한번쯤은 고민해보셔야지~

Copy link
Contributor

Choose a reason for hiding this comment

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

저장소는 CRUD 관련된 prefix만을 사용하려고 합니다.
개인적으로 PRND 컨벤션인 save, fetch도 나쁘지않다고 생각해요

Copy link
Member

Choose a reason for hiding this comment

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

앱을 사용하는 입장에서 보면 유저 상태를 날리는 함수인데 save, fetch라....논리적으로 맞지 않는다고 생각하는데 어떻게 생각하시나요

Copy link
Contributor

@s9hn s9hn Oct 16, 2024

Choose a reason for hiding this comment

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

앱을 사용하는 입장에서 보는게 잘못된 입장이라고 생각해요!
RemoteDataSource 입장에서 보았을 때, 들어온 요청이 '원격 저장소에 있는 유저 상태를 없애줘'니까
deleteUserInfo, removeUserInfo 라는 네이밍이 적절할 것 같아요 혹은 saveDeleted(removed)userInfo으로도 쓸수는 있을것같아요

authService.logOut(request)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AuthRepositoryImpl @Inject constructor(
remoteAuthDataSource.withdraw()
}

override suspend fun logout(pushToken: String): Result<Unit> = runCatching {
override suspend fun logout(pushToken: String) = runCatching {
Copy link
Contributor

Choose a reason for hiding this comment

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

토큰은 로컬 저장소에 저장 및 관리되고 있고 로그아웃은 무조건 본인의 토큰만을 보낼 것 같은데 파라미터로 받는 이유가 있으신가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

pushToken을 파라미터로 받을 이유가 없다는 말씀이신가요..?!
로그아웃 로직을 살펴보니 기존 코드는 파이어베이스에서 pushToken을 가져와 파라미터로 넣어주고 있는 것 같습니다.
파이어베이스에 저장되어 있는 토큰과 로컬 저장소에 저장되어 있는 토큰이 차이점이 있을까요.........?

Copy link
Contributor

Choose a reason for hiding this comment

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

아하 저도 실제 사용처 찾아보니 파이어베이스 토큰을 호출해서 넣어주고 있었네요.
아마 유빈님이 작성하신 코드는 아닌 것으로 보이는데 뷰모델에서 런캐칭으로 Firebase의 토큰을 호출해주고 다음 logout함수에 전달해주고 있는 것 같아요.
저라면 Firebase에서 토큰을 가져오는 행위를 뷰모델에서 하지 않았을 것 같아요.
해당 코드는 레거시 코드이니 이번 PR에서 리팩터링 하실지는 선택에 맡기겠습니다 :)

remoteAuthDataSource.logout(
LogOutRequest(
platform = "Android",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@
package org.sopt.official.auth.impl.source

import org.sopt.official.auth.impl.model.response.LogOutRequest
import org.sopt.official.auth.impl.model.response.LogOutResponse
import org.sopt.official.network.model.request.RefreshRequest
import org.sopt.official.network.model.response.AuthResponse

interface RemoteAuthDataSource {
suspend fun refresh(token: RefreshRequest): AuthResponse
suspend fun withdraw()
suspend fun logout(request: LogOutRequest): LogOutResponse
suspend fun logout(request: LogOutRequest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ package org.sopt.official.network.authenticator
import android.content.Context
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
Expand All @@ -39,6 +37,8 @@ import org.sopt.official.network.model.request.RefreshRequest
import org.sopt.official.network.persistence.SoptDataStore
import org.sopt.official.network.service.RefreshService
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SoptAuthenticator @Inject constructor(
Expand All @@ -50,6 +50,7 @@ class SoptAuthenticator @Inject constructor(
override fun authenticate(route: Route?, response: Response): Request? {
if (response.code == 401) {
val refreshToken = dataStore.refreshToken
if (refreshToken.isEmpty()) return null
val newTokens = runCatching {
runBlocking {
refreshService.refresh(RefreshRequest(refreshToken))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* MIT License
* Copyright 2024 SOPT - Shout Our Passion Together
* Copyright 2023-2024 SOPT - Shout Our Passion Together
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion feature/fortune/src/main/res/drawable/ic_checkbox_off.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!--
MIT License

Copyright (c) 2024 SOPT Makers
Copyright (c) 2022-2024 SOPT Makers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion feature/fortune/src/main/res/drawable/ic_checkbox_on.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!--
MIT License

Copyright (c) 2024 SOPT Makers
Copyright (c) 2023-2024 SOPT Makers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
package org.sopt.official.feature.mypage.model

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable

@Stable
sealed interface MyPageUiModel {
@Immutable
data class Header(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,23 @@
*/
package org.sopt.official.feature.mypage.model

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import org.sopt.official.auth.model.UserActiveState
import org.sopt.official.feature.mypage.mypage.MyPageAction

@Stable
sealed interface MyPageUiState {

@Immutable
data object UnInitialized : MyPageUiState
data class User(val activeState: UserActiveState) : MyPageUiState
data class Dialog(val action: MyPageAction) : MyPageUiState

@Immutable
data class Authenticated(
val activeState: UserActiveState,
val action: MyPageAction? = null
) : MyPageUiState

@Immutable
data object UnAuthenticated : MyPageUiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
Expand All @@ -51,7 +50,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.collections.immutable.persistentListOf
import org.sopt.official.auth.model.UserActiveState
Expand Down Expand Up @@ -84,8 +82,7 @@ class MyPageActivity : AppCompatActivity() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

val isAuthenticated by viewModel.userActiveState.collectAsStateWithLifecycle(initialValue = false)
val dialogState by viewModel.dialogState.collectAsStateWithLifecycle()
val state by viewModel.state.collectAsStateWithLifecycle()
val scrollState = rememberScrollState()

val serviceSectionItems = remember {
Expand Down Expand Up @@ -156,34 +153,25 @@ class MyPageActivity : AppCompatActivity() {
MyPageUiModel.MyPageItem(
title = "로그인",
onItemClick = {
onBackPressedDispatcher.onBackPressed()
startActivity(navigatorProvider.getAuthActivityIntent())
}
)
)
}

LaunchedEffect(Unit) {
args?.userActiveState?.let {
viewModel.setUserActiveState(MyPageUiState.User(it))
viewModel.setUserActiveState(it)
}
}

LaunchedEffect(viewModel.finish, lifecycleOwner) {
viewModel.finish.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect {
ProcessPhoenix.triggerRebirth(context, navigatorProvider.getAuthActivityIntent())
startActivity(navigatorProvider.getAuthActivityIntent())
}
}

if (dialogState is MyPageUiState.Dialog) {
ShowMyPageDialog(
action = (dialogState as MyPageUiState.Dialog).action,
onDismissRequest = viewModel::onDismiss,
onClearSoptampClick = viewModel::resetSoptamp,
onLogoutClick = viewModel::logOut
)
}

Scaffold(modifier = Modifier
.background(SoptTheme.colors.background)
.fillMaxSize(),
Expand All @@ -204,14 +192,45 @@ class MyPageActivity : AppCompatActivity() {
Spacer(modifier = Modifier.height(20.dp))
MyPageSection(items = serviceSectionItems)
Spacer(modifier = Modifier.height(16.dp))
if (isAuthenticated) {
MyPageSection(items = notificationSectionItems)
Spacer(modifier = Modifier.height(16.dp))
MyPageSection(items = soptampSectionItems)
Spacer(modifier = Modifier.height(16.dp))
MyPageSection(items = etcSectionItems)
} else {
MyPageSection(items = etcLoginSectionItems)
when (state) {
is MyPageUiState.Authenticated -> {
when ((state as MyPageUiState.Authenticated).action) {
MyPageAction.CLEAR_SOPTAMP -> {
MyPageDialog(
onDismissRequest = viewModel::onDismiss,
Copy link
Contributor

Choose a reason for hiding this comment

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

dismiss시 수행해야할 동작이 omDismiss인것은 어떤 동작인지 파악하기 힘들 것 같아요. 아래 resetSoptamp처럼 함수가 해야할 일을 직관적으로 적어도 좋을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

함수명에 조금 더 신경 써야겠네요..!!

Copy link
Member

Choose a reason for hiding this comment

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

이 MyPageDialog가 섹션들 사이에 있는 지 이유가 따로 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

처음에 코드를 작성할 때는 uiStateAuthenticated인 상황에서 다이얼로그를 띄우는 거라 섹션들 사이에 같이 넣어뒀었습니다.
그런데 uiStateaction은 분리하는 게 좋을 것 같다고 하셔서 현재는 바꿔주었어요!

title = "미션을 초기화 하실건가요?",
subTitle = "사진, 메모가 삭제되고\n전체 미션이 미완료상태로 초기화됩니다.",
negativeText = "취소",
positiveText = "초기화",
onPositiveButtonClick = viewModel::resetSoptamp
)
}

MyPageAction.LOGOUT -> {
MyPageDialog(
onDismissRequest = viewModel::onDismiss,
title = "로그아웃",
subTitle = "정말 로그아웃을 하실 건가요?",
negativeText = "취소",
positiveText = "로그아웃",
onPositiveButtonClick = viewModel::logOut
)
}

else -> {}
Copy link
Contributor

Choose a reason for hiding this comment

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

가급적이면 sealed나 enum으로 else 분기 없애주는게 좋긴한데 정 안되면 Unit이나 정말 else로 갈 수가 없는 경우이면 익셉션 혹은 에러뷰를 나타내도 괜찮겠어요

}
MyPageSection(items = notificationSectionItems)
Spacer(modifier = Modifier.height(16.dp))
MyPageSection(items = soptampSectionItems)
Spacer(modifier = Modifier.height(16.dp))
MyPageSection(items = etcSectionItems)
}

is MyPageUiState.UnAuthenticated -> {
MyPageSection(items = etcLoginSectionItems)
}

is MyPageUiState.UnInitialized -> {}
}
Spacer(modifier = Modifier.height(32.dp))
}
Expand All @@ -231,35 +250,3 @@ class MyPageActivity : AppCompatActivity() {
}
}
}

@Composable
private fun ShowMyPageDialog(
action: MyPageAction,
onDismissRequest: () -> Unit,
onClearSoptampClick: () -> Unit,
onLogoutClick: () -> Unit
) {
when (action) {
MyPageAction.CLEAR_SOPTAMP -> {
MyPageDialog(
onDismissRequest = onDismissRequest,
title = "미션을 초기화 하실건가요?",
subTitle = "사진, 메모가 삭제되고\n전체 미션이 미완료상태로 초기화됩니다.",
negativeText = "취소",
positiveText = "초기화",
onPositiveButtonClick = onClearSoptampClick
)
}

MyPageAction.LOGOUT -> {
MyPageDialog(
onDismissRequest = onDismissRequest,
title = "로그아웃",
subTitle = "정말 로그아웃을 하실 건가요?",
negativeText = "취소",
positiveText = "로그아웃",
onPositiveButtonClick = onLogoutClick
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import org.sopt.official.auth.model.UserActiveState
Expand All @@ -54,18 +53,15 @@ class MyPageViewModel @Inject constructor(
private val stampRepository: StampRepository,
) : ViewModel() {

private val _userActiveState = MutableStateFlow<MyPageUiState>(MyPageUiState.UnInitialized)
val userActiveState = _userActiveState.filterIsInstance<MyPageUiState.User>()
.map { it.activeState != UserActiveState.UNAUTHENTICATED }

private val _dialogState: MutableStateFlow<MyPageUiState> = MutableStateFlow(MyPageUiState.UnInitialized)
val dialogState: StateFlow<MyPageUiState> = _dialogState.asStateFlow()
private val _state: MutableStateFlow<MyPageUiState> = MutableStateFlow(MyPageUiState.UnInitialized)
val state: StateFlow<MyPageUiState> = _state.asStateFlow()

private val _finish = Channel<Unit>()
val finish = _finish.receiveAsFlow()

fun setUserActiveState(new: MyPageUiState) {
_userActiveState.value = new
fun setUserActiveState(activeState: UserActiveState) {
if (activeState == UserActiveState.UNAUTHENTICATED) _state.value = MyPageUiState.UnAuthenticated
Copy link
Contributor

Choose a reason for hiding this comment

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

아래에선 update를 사용하는데 여기선 세터로 다르게 사용하는 이유가 있나용

Copy link
Member Author

Choose a reason for hiding this comment

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

예리하시군요.. 다른 이유가 있었던 건 아니었습니다..! 코드 통일시켜두도록 하겠습니다!

else _state.value = MyPageUiState.Authenticated(activeState)
}

fun logOut() {
Expand All @@ -87,16 +83,20 @@ class MyPageViewModel @Inject constructor(
fun resetSoptamp() {
viewModelScope.launch {
stampRepository.deleteAllStamps()
.onSuccess { onDismiss() }
.onFailure { Timber.e(it) }
}
}

fun showDialogState(action: MyPageAction) {
_dialogState.tryEmit(MyPageUiState.Dialog(action))
_state.update { currentState ->
(currentState as MyPageUiState.Authenticated).copy(action = action)
}
}

fun onDismiss() {
_dialogState.tryEmit(MyPageUiState.UnInitialized)
_state.update { currentState ->
(currentState as MyPageUiState.Authenticated).copy(action = null)
}
}

}
Loading