Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<application
android:name=".ThipApplication"
android:allowBackup="true"
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
18 changes: 16 additions & 2 deletions app/src/main/java/com/texthip/thip/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,50 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.texthip.thip.data.manager.AuthStateManager
import com.texthip.thip.data.manager.TokenManager
import com.texthip.thip.ui.navigator.navigations.authNavigation
import com.texthip.thip.ui.navigator.routes.CommonRoutes
import com.texthip.thip.ui.theme.ThipTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var tokenManager: TokenManager
@Inject
lateinit var authStateManager: AuthStateManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ThipTheme {
RootNavHost()
RootNavHost(authStateManager)
}
}
// getKakaoKeyHash(this)
}
}

@Composable
fun RootNavHost() {
fun RootNavHost(authStateManager: AuthStateManager) {
val navController = rememberNavController()

LaunchedEffect(Unit) {
authStateManager.tokenExpiredEvent.collectLatest {
navController.navigate(CommonRoutes.Login) {
popUpTo(0) { inclusive = true }
}
}
}

NavHost(
navController = navController,
startDestination = CommonRoutes.Splash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.texthip.thip.data.manager

import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthStateManager @Inject constructor() {
private val _tokenExpiredEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val tokenExpiredEvent = _tokenExpiredEvent.asSharedFlow()

fun triggerTokenExpired() {
_tokenExpiredEvent.tryEmit(Unit)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,10 @@ class UserRepository @Inject constructor(
.handleBaseResponse()
.getOrThrow()
}

suspend fun deleteAccount(): Result<Unit?> = runCatching {
userService.deleteAccount()
.handleBaseResponse()
.getOrThrow()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.texthip.thip.data.model.users.response.SignupResponse
import com.texthip.thip.data.model.users.response.UserSearchResponse
import com.texthip.thip.data.model.users.response.UsersMyFollowingsRecentFeedsResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
Expand Down Expand Up @@ -74,4 +75,7 @@ interface UserService {
@Query("size") size: Int = 30
): BaseResponse<UserSearchResponse>

@DELETE("users")
suspend fun deleteAccount(): BaseResponse<Unit>

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.texthip.thip.ui.common.cards
package com.texthip.thip.ui.common.alarmpage.component

import androidx.compose.foundation.background
import androidx.compose.foundation.border
Expand Down Expand Up @@ -111,18 +111,25 @@ fun CardAlarm(
Column(
horizontalAlignment = Alignment.End
) {
// 안읽음 상태일 때만 빨간 점
if (!isRead) {
Box(
modifier = Modifier
.size(6.dp)
.clip(RoundedCornerShape(3.dp))
.background(color = colors.Red)
)
// 빨간 점을 위한 고정 공간 (항상 6dp 높이 유지)
Box(
modifier = Modifier
.size(6.dp),
contentAlignment = Alignment.Center
) {
// 안읽음 상태일 때만 빨간 점 표시
if (!isRead) {
Box(
modifier = Modifier
.size(6.dp)
.clip(RoundedCornerShape(3.dp))
.background(color = colors.Red)
)
}
}

Text(
text = timeAgo + stringResource(R.string.time_ago),
text = timeAgo,
style = typography.timedate_r400_s11,
color = if (isRead) colors.Grey02 else colors.Grey01,
modifier = Modifier
Expand Down Expand Up @@ -161,7 +168,7 @@ fun PreviewNotificationCards() {
title = "같이 읽기를 시작했어요!",
badgeText = "모임",
message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.",
timeAgo = "12",
timeAgo = "12시간 전",
isRead = isRead
) {
isRead = true
Expand All @@ -172,31 +179,31 @@ fun PreviewNotificationCards() {
title = "같이 읽기를 시작했어요!",
badgeText = "모임",
message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.",
timeAgo = "12",
timeAgo = "12시간 전",
isRead = true
)

CardAlarm(
title = "같이 읽기를 시작했어요!",
badgeText = "피드",
message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.",
timeAgo = "12",
timeAgo = "12시간 전",
isRead = false
)

CardAlarm(
title = "같이 읽기를 시작했어요!",
badgeText = "좋아요",
message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.",
timeAgo = "12",
timeAgo = "12시간 전",
isRead = isRead
)

CardAlarm(
title = "같이 읽기를 시작했어요!",
badgeText = "댓글",
message = "한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다. 한줄만 입력이 가능합니다.",
timeAgo = "12",
timeAgo = "12시간 전",
isRead = isRead
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import com.texthip.thip.R
import com.texthip.thip.ui.common.alarmpage.component.AlarmFilterRow
import com.texthip.thip.ui.common.alarmpage.mock.AlarmItem
import com.texthip.thip.ui.common.cards.CardAlarm
import com.texthip.thip.ui.common.alarmpage.component.CardAlarm
import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class AlarmViewModel : ViewModel() {
_alarmItems.value = listOf(
AlarmItem(1, "피드", "내 글을 좋아합니다.", "user123님이 내 글에 좋아요를 눌렀어요.", "2시간 전", false),
AlarmItem(2, "모임", "같이 읽기를 시작했어요!", "모임방에서 20분 동안 같이 읽기가 시작되었어요!", "7시간 전", false),
AlarmItem(3, "피드", "내 글에 댓글이 달렸어요.", "user1: 진짜 공감합니다!", "2025.01.12", true),
AlarmItem(4, "모임", "투표가 시작되었어요!", "투표지를 먼저 열람합니다.", "17시간 전", false),
AlarmItem(5, "피드", "팔로워가 새 글을 올렸어요.", "user456님이 새 리뷰를 작성했습니다.", "1일 전", true),
AlarmItem(6, "모임", "새로운 모임방 초대", "호르몬 체인지 완독하는 방에 초대되었습니다.", "2일 전", false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private fun EmptyMySubscriptionBar() {
modifier = Modifier
.fillMaxWidth()
.height(42.dp)
.clip(RoundedCornerShape(8.dp))
.clip(RoundedCornerShape(12.dp))
.background(colors.DarkGrey02)
.clickable { }
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.texthip.thip.ui.mypage.screen

import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -12,15 +13,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
Expand All @@ -29,22 +33,44 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.texthip.thip.R
import com.texthip.thip.ui.common.buttons.CheckboxButton
import com.texthip.thip.ui.common.modal.DialogPopup
import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar
import com.texthip.thip.ui.mypage.viewmodel.DeleteAccountViewModel
import com.texthip.thip.ui.theme.DarkGrey02
import com.texthip.thip.ui.theme.Red
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography

@Composable
fun DeleteAccountScreen(
onNavigateBack: () -> Unit
onNavigateBack: () -> Unit,
onNavigateToLogin: () -> Unit,
viewModel: DeleteAccountViewModel = hiltViewModel()
) {
val context = LocalContext.current
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value

var isChecked by rememberSaveable { mutableStateOf(false) }
val backgroundColor = if (isChecked) colors.Purple else colors.Grey02
var isDialogVisible by rememberSaveable { mutableStateOf(false) }

// 회원탈퇴 완료 시 로그인 화면으로 이동
LaunchedEffect(uiState.isDeleteCompleted) {
if (uiState.isDeleteCompleted) {
onNavigateToLogin()
}
}

// 에러 메시지 표시
LaunchedEffect(uiState.errorMessage) {
uiState.errorMessage?.let { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}

Column(
Modifier
Expand Down Expand Up @@ -159,19 +185,30 @@ fun DeleteAccountScreen(
onCancel = { isDialogVisible = false },
onConfirm = {
isDialogVisible = false
// TODO: 회원탈퇴 로직
viewModel.deleteAccount(context)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ViewModel에 Android Context 전달 지양

viewModel.deleteAccount(context)는 누수/테스트성 저하 위험이 있습니다. UI 의존은 Composable로, 앱 컨텍스트가 필요하면 @ApplicationContext로 주입하세요.

권장 diff(호출부):

-                            viewModel.deleteAccount(context)
+                            viewModel.deleteAccount()

ViewModel 예시(참고):

class DeleteAccountViewModel @Inject constructor(
    @ApplicationContext private val appContext: Context,
    private val repo: UserRepository,
    private val tokenManager: TokenManager
) : ViewModel() {
    fun deleteAccount() { /* ... */ }
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt
around line 188, remove the direct pass of an Android Context into the ViewModel
(viewModel.deleteAccount(context)); instead update the ViewModel to receive
@ApplicationContext-injected Context via its constructor (or otherwise use
application-scoped dependencies) and expose a parameterless deleteAccount()
method; change the call in this composable to viewModel.deleteAccount(); move
any context-dependent work (e.g., file/SharedPreferences/Toast) into the
ViewModel using the injected app context and adjust DI/Hilt bindings to provide
@ApplicationContext to the ViewModel.

}
)
}
}
}

// 로딩 중일 때 전체 화면에 로딩 인디케이터 표시
if (uiState.isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
Comment on lines +195 to +203
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로딩 오버레이가 Column 자식으로는 실제 오버레이/터치 차단이 되지 않습니다

현재 구조에서는 하위에 쌓여 터치가 막히지 않을 수 있습니다. 삭제 중 중복 탭/중복 호출을 방지하려면 모달 다이얼로그로 띄워주세요.

권장 diff:

-        if (uiState.isLoading) {
-            Box(
-                modifier = Modifier.fillMaxSize(),
-                contentAlignment = Alignment.Center
-            ) {
-                CircularProgressIndicator()
-            }
-        }
+        if (uiState.isLoading) {
+            Dialog(
+                onDismissRequest = {},
+                properties = androidx.compose.ui.window.DialogProperties(
+                    dismissOnBackPress = false,
+                    dismissOnClickOutside = false
+                )
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(24.dp),
+                    contentAlignment = Alignment.Center
+                ) {
+                    CircularProgressIndicator()
+                }
+            }
+        }

(필요 시 import: import androidx.compose.ui.window.DialogProperties)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 로딩 중일 때 전체 화면에 로딩 인디케이터 표시
if (uiState.isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
// 로딩 중일 때 전체 화면에 로딩 인디케이터 표시
if (uiState.isLoading) {
Dialog(
onDismissRequest = {},
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt
around lines 195-203, the current loading indicator is a Box inside the Column
so it may not block touches to underlying content; replace this in-column
overlay with a modal Dialog (using androidx.compose.ui.window.Dialog and
DialogProperties) so the loading state presents a full-screen modal that
prevents interaction and duplicate taps/calls; create a Dialog with an empty
onDismissRequest (or disable dismiss) and place a centered
CircularProgressIndicator inside, and add the necessary import for
DialogProperties.

}
}

@Preview
@Composable
private fun DeleteAccountScreenPrev() {
DeleteAccountScreen(
onNavigateBack = {}
onNavigateBack = {},
onNavigateToLogin = {}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -24,7 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.texthip.thip.R
import com.texthip.thip.ui.common.buttons.OptionChipButton
import com.texthip.thip.ui.common.cards.CardAlarm
import com.texthip.thip.ui.common.alarmpage.component.CardAlarm
import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar
import com.texthip.thip.ui.mypage.mock.ReactionItem
import com.texthip.thip.ui.theme.ThipTheme
Expand Down
Loading