From 44a20e3aeed6634b4eff930c31ff137bab79f229 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 14:05:52 +0900 Subject: [PATCH 01/15] =?UTF-8?q?=EA=B4=84=ED=98=B8=20=EC=97=86=EC=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/playfriends/ui/screen/ProfileScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt index 4126d89..536ae55 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt @@ -164,9 +164,9 @@ fun ProfileScreen( Text( buildAnnotatedString { withStyle(SpanStyle(color = Color(0xFF228B22), fontWeight = FontWeight.Bold)) { - append("[${user?.username ?: "고객"}]") + append("${user?.username ?: "고객"}") } - append("님을 위한 취향 분석 레포트") + append(" 님을 위한 취향 분석 레포트") }, fontSize = 18.sp ) From 999473c98401ce559592211ea900169805b8bc18 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 16:00:16 +0900 Subject: [PATCH 02/15] =?UTF-8?q?=ED=99=88=ED=99=94=EB=A9=B4=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=ED=8C=9D=EC=97=85=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/HomeScreen.kt | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index d80f840..ed09cc5 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -102,7 +102,7 @@ fun HomeScreen( // 팝업 관련 상태 var showCreateGroupDialog by remember { mutableStateOf(false) } var showJoinGroupDialog by remember { mutableStateOf(false) } - var showGroupCreatedDialog by remember { mutableStateOf(false) } + var showGroupCreatedDialog by remember { mutableStateOf(null) } var groupName by remember { mutableStateOf("") } var groupId by remember { mutableStateOf("") } var showInputErrorDialog by remember { mutableStateOf(false) } @@ -117,6 +117,8 @@ fun HomeScreen( val groupOperationState by groupViewModel.groupOperationState.collectAsState() val selectedGroup by groupViewModel.selectedGroup.collectAsState() var createdGroupId by remember { mutableStateOf("") } + // 그룹 생성 팝업을 특정 그룹에만 일시적으로 띄우기 위한 상태 + var createdGroupIdForDialog by remember { mutableStateOf("") } // 추가: UserViewModel 선언 및 상태 수집 val userViewModel: UserViewModel = viewModel(viewModelStoreOwner = activity) @@ -189,6 +191,11 @@ fun HomeScreen( var navigateToHome by remember { mutableStateOf(false) } + // showGroupCreatedDialog 값이 변경될 때마다 로그 출력 + LaunchedEffect(showGroupCreatedDialog) { + Log.d("HomeScreen", "showGroupCreatedDialog: $showGroupCreatedDialog") + } + Scaffold( containerColor = backgroundColor, topBar = { @@ -298,6 +305,9 @@ fun HomeScreen( onMemberClick = { memberDialogLoading = true showMemberDialog = true + showGroupCreatedDialog = false // 팝업 강제 종료 + groupViewModel.resetOperationState() // 그룹 생성 성공 상태도 초기화 + Log.d("HomeScreen", "onMemberClick - showGroupCreatedDialog: $showGroupCreatedDialog") memberNames = listOf() groupViewModel.getGroup(group.id) } @@ -549,13 +559,22 @@ fun HomeScreen( } // 그룹 생성 완료 팝업 + // 그룹 생성 성공 시에만 해당 그룹에 대해 팝업을 띄움 LaunchedEffect(groupOperationState, selectedGroup) { val group = selectedGroup - if (groupOperationState is GroupViewModel.GroupOperationState.Success && group != null) { + if (groupOperationState is GroupViewModel.GroupOperationState.Success + && group != null + && (groupOperationState as GroupViewModel.GroupOperationState.Success).message == "그룹이 생성되었습니다" + ) { createdGroupId = group._id + createdGroupIdForDialog = group._id // 반드시 동시에 세팅 + if (showGroupCreatedDialog == null) { + showGroupCreatedDialog = true // 최초 1회만 true + } else { + showGroupCreatedDialog = false // 그 뒤로는 무조건 false + } groupName = group.groupname // 생성된 그룹 이름을 저장 - showGroupCreatedDialog = true - // showCreateGroupDialog = false // 필요시 생성 팝업 닫기 + groupViewModel.resetOperationState() // Success 상태 즉시 초기화 } } if (groupOperationState is GroupViewModel.GroupOperationState.Error) { @@ -571,7 +590,7 @@ fun HomeScreen( } ) } - if (showGroupCreatedDialog) { + if (showGroupCreatedDialog == true) { val clipboardManager = LocalClipboardManager.current AlertDialog( onDismissRequest = { showGroupCreatedDialog = false }, @@ -620,6 +639,8 @@ fun HomeScreen( Button( onClick = { showGroupCreatedDialog = false + createdGroupIdForDialog = "" + groupViewModel.resetOperationState() navigateToHome = true }, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4C6A57)) From e802897a7f8a439437e86e53838015683ba8fe8d Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 16:50:05 +0900 Subject: [PATCH 03/15] =?UTF-8?q?=EC=86=8C=EC=86=8D=20=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=ED=99=88=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/HomeScreen.kt | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index d80f840..1b58215 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -102,7 +102,7 @@ fun HomeScreen( // 팝업 관련 상태 var showCreateGroupDialog by remember { mutableStateOf(false) } var showJoinGroupDialog by remember { mutableStateOf(false) } - var showGroupCreatedDialog by remember { mutableStateOf(false) } + var showGroupCreatedDialog by remember { mutableStateOf(null) } var groupName by remember { mutableStateOf("") } var groupId by remember { mutableStateOf("") } var showInputErrorDialog by remember { mutableStateOf(false) } @@ -117,6 +117,8 @@ fun HomeScreen( val groupOperationState by groupViewModel.groupOperationState.collectAsState() val selectedGroup by groupViewModel.selectedGroup.collectAsState() var createdGroupId by remember { mutableStateOf("") } + // 그룹 생성 팝업을 특정 그룹에만 일시적으로 띄우기 위한 상태 + var createdGroupIdForDialog by remember { mutableStateOf("") } // 추가: UserViewModel 선언 및 상태 수집 val userViewModel: UserViewModel = viewModel(viewModelStoreOwner = activity) @@ -189,6 +191,11 @@ fun HomeScreen( var navigateToHome by remember { mutableStateOf(false) } + // showGroupCreatedDialog 값이 변경될 때마다 로그 출력 + LaunchedEffect(showGroupCreatedDialog) { + Log.d("HomeScreen", "showGroupCreatedDialog: $showGroupCreatedDialog") + } + Scaffold( containerColor = backgroundColor, topBar = { @@ -276,32 +283,47 @@ fun HomeScreen( // 그룹 데이터 정의 // 그룹 카드들 렌더링 - groups.forEach { group -> - AccordionGroupCard( - group = group, - isExpanded = expandedGroupId == group.id, - onToggle = { - expandedGroupId = if (expandedGroupId == group.id) null else group.id - }, - onGroupClick = { - // 상세정보가 열린 상태에서 한 번 더 클릭하면 GroupScreen으로 이동 - if (expandedGroupId == group.id) { - navController.navigate("group/${group.id}") + if (groups.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize() + .padding(top = 30.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "그룹 정보가 없습니다.\n\n하단의 + 버튼을 눌러\n지금 바로 그룹에 참여해보세요!", + fontSize = 14.sp, + color = Color.Gray, + textAlign = TextAlign.Center + ) + } + } else { + groups.forEach { group -> + AccordionGroupCard( + group = group, + isExpanded = expandedGroupId == group.id, + onToggle = { + expandedGroupId = if (expandedGroupId == group.id) null else group.id + }, + onGroupClick = { + // 상세정보가 열린 상태에서 한 번 더 클릭하면 GroupScreen으로 이동 + if (expandedGroupId == group.id) { + navController.navigate("group/${group.id}") + } + }, + cardBackground = cardBackground, + titleColor = titleColor, + chipColor = chipColor, + moveWalkColor = moveWalkColor, + moveSubwayColor = moveSubwayColor, + onMemberClick = { + memberDialogLoading = true + showMemberDialog = true + memberNames = listOf() + groupViewModel.selectGroup(null) // 먼저 null로 초기화 + groupViewModel.getGroup(group.id) } - }, - cardBackground = cardBackground, - titleColor = titleColor, - chipColor = chipColor, - moveWalkColor = moveWalkColor, - moveSubwayColor = moveSubwayColor, - // 멤버 아이콘 클릭 핸들러 추가 - onMemberClick = { - memberDialogLoading = true - showMemberDialog = true - memberNames = listOf() - groupViewModel.getGroup(group.id) - } - ) + ) + } } Spacer(modifier = Modifier.height(80.dp)) @@ -549,13 +571,20 @@ fun HomeScreen( } // 그룹 생성 완료 팝업 + // 그룹 생성 성공 시에만 해당 그룹에 대해 팝업을 띄움 LaunchedEffect(groupOperationState, selectedGroup) { val group = selectedGroup - if (groupOperationState is GroupViewModel.GroupOperationState.Success && group != null) { + if (groupOperationState is GroupViewModel.GroupOperationState.Success + && group != null + && (groupOperationState as GroupViewModel.GroupOperationState.Success).message == "그룹이 생성되었습니다" + && showGroupCreatedDialog == null + ) { createdGroupId = group._id - groupName = group.groupname // 생성된 그룹 이름을 저장 + createdGroupIdForDialog = group._id showGroupCreatedDialog = true - // showCreateGroupDialog = false // 필요시 생성 팝업 닫기 + groupName = group.groupname // 생성된 그룹 이름을 저장 + groupViewModel.resetOperationState() + groupViewModel.selectGroup(null) // selectedGroup도 초기화 } } if (groupOperationState is GroupViewModel.GroupOperationState.Error) { @@ -571,10 +600,15 @@ fun HomeScreen( } ) } - if (showGroupCreatedDialog) { + if (showGroupCreatedDialog == true) { val clipboardManager = LocalClipboardManager.current AlertDialog( - onDismissRequest = { showGroupCreatedDialog = false }, + onDismissRequest = { + showGroupCreatedDialog = false + createdGroupId = "" + groupViewModel.resetOperationState() + groupViewModel.selectGroup(null) + }, title = { Text( "그룹이 생성되었습니다!", @@ -620,6 +654,9 @@ fun HomeScreen( Button( onClick = { showGroupCreatedDialog = false + createdGroupId = "" + groupViewModel.resetOperationState() + groupViewModel.selectGroup(null) navigateToHome = true }, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4C6A57)) From 2f968762e73e238244cd1638ef4f50d1d9c0dbd9 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 17:13:02 +0900 Subject: [PATCH 04/15] =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=20=ED=8C=9D=EC=97=85=20=EC=B6=94=EA=B0=80,=20=ED=99=88?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/HomeScreen.kt | 39 ++++++++++++++++++- .../playfriends/ui/viewmodel/UserViewModel.kt | 37 ++++++++++-------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index 1b58215..fd2e07b 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -70,6 +70,7 @@ import android.util.Log import androidx.activity.ComponentActivity import android.app.Activity import android.content.Context +import androidx.compose.material.icons.filled.Person import androidx.compose.ui.text.style.TextOverflow import androidx.compose.material3.AlertDialog @@ -196,6 +197,10 @@ fun HomeScreen( Log.d("HomeScreen", "showGroupCreatedDialog: $showGroupCreatedDialog") } + // 그룹 참여 성공 안내 팝업 상태 변수 선언 + var showJoinSuccessDialog by remember { mutableStateOf(false) } + var joinedGroupName by remember { mutableStateOf("") } + Scaffold( containerColor = backgroundColor, topBar = { @@ -776,6 +781,11 @@ fun HomeScreen( joinResultMessage = (joinGroupState as UserViewModel.JoinGroupState.Success).message showJoinResultDialog = true showJoinGroupDialog = false + // 참여한 그룹명 추출 (userGroups에서 가장 최근 추가된 그룹명 사용) + val latestGroup = userGroups.lastOrNull() + joinedGroupName = latestGroup?.groupname ?: "" + showJoinSuccessDialog = true + // navController.navigate("home") { ... }는 팝업 확인 버튼에서 실행 } is UserViewModel.JoinGroupState.Error -> { joinResultMessage = (joinGroupState as UserViewModel.JoinGroupState.Error).message @@ -819,7 +829,16 @@ fun HomeScreen( } else { Column { memberNames.forEach { name -> - Text(name, fontSize = 16.sp) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "Members", + tint = Color.Black, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(name, fontSize = 16.sp) + } } } } @@ -831,6 +850,24 @@ fun HomeScreen( } ) } + // 그룹 참여 성공 안내 팝업 + if (showJoinSuccessDialog && joinedGroupName.isNotBlank()) { + AlertDialog( + onDismissRequest = { showJoinSuccessDialog = false }, + title = { Text("그룹 참여 성공", fontWeight = FontWeight.Bold, fontSize = 18.sp) }, + text = { Text("'${joinedGroupName}' 그룹에 성공적으로 참여했습니다.", fontSize = 16.sp) }, + confirmButton = { + Button(onClick = { + showJoinSuccessDialog = false + navController.navigate("home") { + popUpTo("home") { inclusive = true } + } + }) { + Text("확인") + } + } + ) + } } } diff --git a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt index 00158af..dfc9156 100644 --- a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt @@ -14,20 +14,20 @@ import retrofit2.http.HEAD class UserViewModel : ViewModel() { private val userRepository = UserRepository() - + // 상태 관리 private val _loginState = MutableStateFlow(LoginState.Idle) val loginState: StateFlow = _loginState.asStateFlow() - + private val _user = MutableStateFlow(null) val user: StateFlow = _user.asStateFlow() private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading.asStateFlow() - + private val _userGroups = MutableStateFlow>(emptyList()) val userGroups: StateFlow> = _userGroups.asStateFlow() - + // 그룹 참여 상태 private val _joinGroupState = MutableStateFlow(JoinGroupState.Idle) val joinGroupState: StateFlow = _joinGroupState.asStateFlow() @@ -38,7 +38,7 @@ class UserViewModel : ViewModel() { data class Success(val message: String) : JoinGroupState() data class Error(val message: String) : JoinGroupState() } - + // 로그인 상태 sealed class LoginState { object Idle : LoginState() @@ -47,15 +47,15 @@ class UserViewModel : ViewModel() { data class SuccessMessage(val message: String) : LoginState() data class Error(val message: String) : LoginState() } - + // 로그인 fun login(userid: String, password: String, autoLogin: Boolean = false) { viewModelScope.launch { _loginState.value = LoginState.Loading - + val loginRequest = LoginRequest(userid, password, autoLogin) val result = userRepository.login(loginRequest) - + result.fold( onSuccess = { token -> _loginState.value = LoginState.Success(token) @@ -68,7 +68,7 @@ class UserViewModel : ViewModel() { ) } } - + // 로그아웃 fun logout() { viewModelScope.launch { @@ -93,15 +93,15 @@ class UserViewModel : ViewModel() { ) } } - + // 회원가입 fun createUser(userid: String, username: String, password: String) { viewModelScope.launch { _loginState.value = LoginState.Loading - + val userCreate = UserCreate(userid, username, password = password) val result = userRepository.createUser(userCreate) - + result.fold( onSuccess = { user -> // 회원가입 성공 시 사용자 정보 저장 및 로그 출력 @@ -116,7 +116,7 @@ class UserViewModel : ViewModel() { ) } } - + // 현재 사용자 정보 조회 fun getCurrentUser() { viewModelScope.launch { @@ -137,7 +137,7 @@ class UserViewModel : ViewModel() { ) } } - + // 사용자의 그룹 목록 조회 fun getUserGroups(userId: String) { viewModelScope.launch { @@ -153,15 +153,18 @@ class UserViewModel : ViewModel() { ) } } - + // 그룹 참여 fun joinGroup(groupId: String) { viewModelScope.launch { _joinGroupState.value = JoinGroupState.Loading + val result = userRepository.joinGroup(groupId) result.fold( onSuccess = { message -> _joinGroupState.value = JoinGroupState.Success(message) + // 그룹 목록 새로고침 + user.value?.let { getUserGroups(it.userid) } }, onFailure = { exception -> _joinGroupState.value = JoinGroupState.Error(exception.message ?: "그룹 참여 실패") @@ -174,12 +177,12 @@ class UserViewModel : ViewModel() { fun resetJoinGroupState() { _joinGroupState.value = JoinGroupState.Idle } - + // 로그인 상태 초기화 fun resetLoginState() { _loginState.value = LoginState.Idle } - + // 에러 메시지 설정 fun setError(message: String) { _loginState.value = LoginState.Error(message) From 3e17c09f3f874ccae70ad178d8b0fe19680ac1c5 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 17:31:38 +0900 Subject: [PATCH 05/15] =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/HomeScreen.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index 9a6ffee..74af719 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -781,6 +781,11 @@ fun HomeScreen( joinResultMessage = (joinGroupState as UserViewModel.JoinGroupState.Success).message showJoinResultDialog = true showJoinGroupDialog = false + // 참여한 그룹명 추출 (userGroups에서 가장 최근 추가된 그룹명 사용) + val latestGroup = userGroups.lastOrNull() + joinedGroupName = latestGroup?.groupname ?: "" + showJoinSuccessDialog = true + // navController.navigate("home") { ... }는 팝업 확인 버튼에서 실행 } is UserViewModel.JoinGroupState.Error -> { joinResultMessage = (joinGroupState as UserViewModel.JoinGroupState.Error).message @@ -824,7 +829,16 @@ fun HomeScreen( } else { Column { memberNames.forEach { name -> - Text(name, fontSize = 16.sp) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "Members", + tint = Color.Black, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(name, fontSize = 16.sp) + } } } } @@ -836,6 +850,24 @@ fun HomeScreen( } ) } + // 그룹 참여 성공 안내 팝업 + if (showJoinSuccessDialog && joinedGroupName.isNotBlank()) { + AlertDialog( + onDismissRequest = { showJoinSuccessDialog = false }, + title = { Text("그룹 참여 완료", fontWeight = FontWeight.Bold, fontSize = 18.sp) }, + text = { Text("'${joinedGroupName}' 그룹에 성공적으로 참여했습니다.", fontSize = 16.sp) }, + confirmButton = { + Button(onClick = { + showJoinSuccessDialog = false + navController.navigate("home") { + popUpTo("home") { inclusive = true } + } + }) { + Text("확인") + } + } + ) + } } } From cb1d7b596ef4a88e2218e383247ca820345a5f4a Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 20:42:36 +0900 Subject: [PATCH 06/15] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=ED=9B=84=20=EC=9D=B4=EB=8F=99=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=ED=99=94=EB=A9=B4=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/UserRepository.kt | 2 +- .../playfriends/ui/navigation/AuthState.kt | 2 +- .../playfriends/ui/screen/GroupScreen.kt | 168 ++++++++++++--- .../playfriends/ui/screen/HomeScreen.kt | 2 +- .../playfriends/ui/screen/LoginScreen.kt | 200 +++++++++--------- 5 files changed, 245 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt b/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt index 641d594..49c3ea5 100644 --- a/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt +++ b/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt @@ -197,7 +197,7 @@ class UserRepository { Result.failure(Exception(errorMsg)) } } catch (e: Exception) { - Result.failure(Exception("그룹 참여 중 오류가 발생했습니다: ${e.message}")) + Result.failure(Exception("그룹 참여 중 오류가 발생했습니다.")) } } diff --git a/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt b/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt index f967406..eb49d4c 100644 --- a/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt +++ b/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt @@ -21,7 +21,7 @@ fun rememberAuthState(navController: NavController) { val loginState by userViewModel.loginState.collectAsState() LaunchedEffect(user, loginState) { - if (loginState is UserViewModel.LoginState.Success || (user != null && loginState !is UserViewModel.LoginState.Idle)) { + if (loginState is UserViewModel.LoginState.Success) { navController.navigate("home") { popUpTo("splash") { inclusive = true } launchSingleTop = true diff --git a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt index 2dcd207..7a60924 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt @@ -44,10 +44,50 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.geometry.Offset import androidx.lifecycle.viewmodel.compose.viewModel import com.example.playfriends.ui.viewmodel.GroupViewModel import com.example.playfriends.ui.viewmodel.UserViewModel import android.util.Log +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.TextButton +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.platform.LocalContext +import android.widget.Toast +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.res.painterResource +import com.example.playfriends.R +import java.text.SimpleDateFormat +import java.util.Locale +import androidx.compose.ui.text.style.TextAlign + +// 커스텀 스낵바 컴포저블 추가 +@Composable +fun CustomSnackbar(message: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .background(Color.White, shape = RoundedCornerShape(12.dp)) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.logo), + contentDescription = "앱 로고", + modifier = Modifier.size(28.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = message, + color = Color(0xFF228B22), + fontWeight = FontWeight.Bold, + fontSize = 15.sp, + modifier = Modifier.weight(1f) + ) + } +} @Composable fun GroupScreen( @@ -79,6 +119,14 @@ fun GroupScreen( "당구장", "보드게임카페", "만화카페", "PC방", "스포츠센터", "수영장" ) + val snackbarHostState = remember { SnackbarHostState() } + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current + + // 스낵바 상태 추가 + var showSnackbar by remember { mutableStateOf(false) } + var snackbarMsg by remember { mutableStateOf("") } + LaunchedEffect(groupId) { groupViewModel.getGroup(groupId) } @@ -102,7 +150,12 @@ fun GroupScreen( profileInitial = currentUser?.username?.first()?.toString() ?: "A" ) }, - containerColor = backgroundColor + containerColor = backgroundColor, + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) { data -> + CustomSnackbar(message = data.visuals.message) + } + } ) { padding -> if (group == null) { // 로딩 또는 에러 처리 @@ -112,6 +165,14 @@ fun GroupScreen( return@Scaffold } + // 스낵바 상태 감지해서 띄우기 + if (showSnackbar) { + LaunchedEffect(snackbarMsg) { + snackbarHostState.showSnackbar(snackbarMsg) + showSnackbar = false + } + } + val isOwner = group?.owner_id == currentUser?._id Column( @@ -119,33 +180,78 @@ fun GroupScreen( .padding(padding) .fillMaxSize() .verticalScroll(scrollState) - .padding(horizontal = 24.dp) + .padding(start = 30.dp, end = 30.dp, top = 10.dp) ) { Spacer(modifier = Modifier.height(16.dp)) - // 그룹명 + 그룹코드 + 시간/위치 - Row( + // 그룹명/날짜 + 초대코드/위치 (2줄로 분리, 양끝 배치) + Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .padding(horizontal = 0.dp) ) { - Column(horizontalAlignment = Alignment.Start) { - Text(group!!.groupname, fontSize = 26.sp, fontWeight = FontWeight.Bold, color = titleColor) - Text(group!!._id, fontSize = 12.sp, color = Color.Gray) // 그룹 코드를 ID로 임시 대체 + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + group!!.groupname, + fontSize = 26.sp, + fontWeight = FontWeight.Bold, + color = titleColor + ) + // 일정 포맷팅: 25/07/20 00:00 - 25/07/20 20:00 + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) + val outputFormat = SimpleDateFormat("yy/MM/dd HH:mm", Locale.getDefault()) + val start = try { + outputFormat.format(inputFormat.parse(group!!.starttime) ?: "") + } catch (e: Exception) { "" } + val end = try { + group!!.endtime?.let { outputFormat.format(inputFormat.parse(it) ?: "") } ?: "" + } catch (e: Exception) { "" } + val timeStr = if (end.isNotBlank()) "$start - $end" else start + Text( + timeStr, + fontSize = 14.sp, + color = Color.Black, + textAlign = TextAlign.End, + modifier = Modifier + .weight(1f) + .padding(start = 12.dp) + ) } - Column(horizontalAlignment = Alignment.End) { - Text(group!!.starttime, fontSize = 16.sp, color = Color.Black) // 시간 포맷팅 필요 + // 하단: 초대코드 - 위치 + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + TextButton( + onClick = { + clipboardManager.setText(AnnotatedString(group!!._id)) + Toast.makeText(context, "복사가 완료되었습니다.", Toast.LENGTH_SHORT).show() + }, + contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp), + modifier = Modifier.height(IntrinsicSize.Min) + ) { + Text(group!!._id, fontSize = 12.sp, color = Color.Gray) + } Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.LocationOn, contentDescription = "location", modifier = Modifier.size(18.dp)) Spacer(modifier = Modifier.width(6.dp)) - Text("강남", fontSize = 16.sp) // 위치 정보 필요 + Text("대전", fontSize = 16.sp) } } } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(15.dp)) // 참여자 카드 group?.let { currentGroup -> @@ -269,27 +375,27 @@ fun OwnerScheduleCreationUI( modifier = Modifier .size(240.dp) ) { - Canvas(modifier = Modifier.fillMaxSize()) { - val hexPath = Path() - val radius = size.minDimension / 2f * 0.9f - val centerX = size.width / 2f - val centerY = size.height / 2f - for (i in 0..5) { - val angle = Math.toRadians((60.0 * i - 30.0)) - val x = centerX + radius * Math.cos(angle).toFloat() - val y = centerY + radius * Math.sin(angle).toFloat() - if (i == 0) { - hexPath.moveTo(x, y) - } else { - hexPath.lineTo(x, y) + Canvas(modifier = Modifier.fillMaxSize()) { + val hexPath = Path() + val radius = size.minDimension / 2f * 0.9f + val centerX = size.width / 2f + val centerY = size.height / 2f + for (i in 0..5) { + val angle = Math.toRadians((60.0 * i - 30.0)) + val x = centerX + radius * Math.cos(angle).toFloat() + val y = centerY + radius * Math.sin(angle).toFloat() + if (i == 0) { + hexPath.moveTo(x, y) + } else { + hexPath.lineTo(x, y) + } } + hexPath.close() + drawPath(path = hexPath, color = Color(0xFFD1E9D1), style = Fill) + drawPath(path = hexPath, color = Color(0xFFB0EACD), style = Stroke(width = 6f)) } - hexPath.close() - drawPath(path = hexPath, color = Color(0xFFD1E9D1), style = Fill) - drawPath(path = hexPath, color = Color(0xFFB0EACD), style = Stroke(width = 6f)) } } -} Spacer(modifier = Modifier.height(30.dp)) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index 74af719..cd5797e 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -648,7 +648,7 @@ fun HomeScreen( } } Text( - "Join Group 버튼을 누르고 이 초대코드를 입력하면 ${groupName} 그룹에 참여할 수 있습니다.\n초대코드는 그룹 창에서도 확인할 수 있습니다.", + "Join Group 버튼을 누르고 이 초대코드를 입력하면 ${groupName} 그룹에 참여할 수 있습니다.\n각 그룹창에 있는 초대코드를 클릭하면 초대코드를 복사할 수 있습니다.", fontSize = 14.sp, color = Color.Gray, modifier = Modifier.padding(top = 8.dp) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt index 9483b6b..f3eb0bd 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt @@ -30,6 +30,7 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.compose.ui.platform.LocalContext import com.example.playfriends.ui.screen.findActivity +import android.widget.Toast @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -54,7 +55,7 @@ fun LoginScreen( val buttonColor = Color(0xFF5C9E5C) val borderColor = Color(0xFF8DB38C) val errorColor = Color(0xFFE57373) - + // Black Han Sans 폰트 사용 (Google Fonts) val fontFamily = FontFamily( Font(googleFont = GoogleFont("Black Han Sans"), fontProvider = GoogleFont.Provider( @@ -67,7 +68,7 @@ fun LoginScreen( // ViewModel 상태 관찰 val loginState by userViewModel.loginState.collectAsState() val user by userViewModel.user.collectAsState() - + // 사용자 정보 변경 시 로그 출력 LaunchedEffect(user) { user?.let { @@ -199,10 +200,19 @@ fun LoginScreen( Spacer(modifier = Modifier.height(24.dp)) + val context = LocalContext.current Button( onClick = { - if (userId.isNotBlank() && password.isNotBlank()) { - userViewModel.login(userId, password) + when { + userId.isBlank() -> { + Toast.makeText(context, "아이디를 입력해주세요", Toast.LENGTH_SHORT).show() + } + password.isBlank() -> { + Toast.makeText(context, "비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show() + } + else -> { + userViewModel.login(userId, password) + } } }, modifier = Modifier @@ -304,108 +314,108 @@ fun LoginScreen( } } - if (step == 1) { - OutlinedTextField( - value = userNickname, - onValueChange = { userNickname = it }, - placeholder = { Text("닉네임", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true - ) + if (step == 1) { + OutlinedTextField( + value = userNickname, + onValueChange = { userNickname = it }, + placeholder = { Text("닉네임", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true + ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - OutlinedTextField( - value = userId, - onValueChange = { userId = it }, - placeholder = { Text("아이디", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true - ) + OutlinedTextField( + value = userId, + onValueChange = { userId = it }, + placeholder = { Text("아이디", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true + ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - OutlinedTextField( - value = password, - onValueChange = { password = it }, - placeholder = { Text("비밀번호", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - visualTransformation = PasswordVisualTransformation(), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true - ) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + placeholder = { Text("비밀번호", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + visualTransformation = PasswordVisualTransformation(), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true + ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(24.dp)) - Button( - onClick = { - // 입력 검증 - when { - userNickname.isBlank() -> { - // 닉네임이 비어있으면 에러 메시지 표시 - userViewModel.setError("닉네임을 입력해주세요.") - } - userId.isBlank() -> { - // 아이디가 비어있으면 에러 메시지 표시 - userViewModel.setError("아이디를 입력해주세요.") - } - userId.length < 3 -> { - // 아이디가 3자 미만이면 에러 메시지 표시 - userViewModel.setError("아이디는 3자 이상으로 입력해주세요.") - } - password.length < 8 -> { - // 비밀번호가 8자 미만이면 에러 메시지 표시 - userViewModel.setError("비밀번호는 8자 이상으로 입력해주세요.") - } - else -> { - // 모든 검증 통과 시 회원가입 API 호출 - Log.d("LoginScreen", "회원가입 시도: $userId, $userNickname") - userViewModel.createUser(userId, userNickname, password) + Button( + onClick = { + // 입력 검증 + when { + userNickname.isBlank() -> { + // 닉네임이 비어있으면 에러 메시지 표시 + userViewModel.setError("닉네임을 입력해주세요.") + } + userId.isBlank() -> { + // 아이디가 비어있으면 에러 메시지 표시 + userViewModel.setError("아이디를 입력해주세요.") + } + userId.length < 3 -> { + // 아이디가 3자 미만이면 에러 메시지 표시 + userViewModel.setError("아이디는 3자 이상으로 입력해주세요.") + } + password.length < 8 -> { + // 비밀번호가 8자 미만이면 에러 메시지 표시 + userViewModel.setError("비밀번호는 8자 이상으로 입력해주세요.") + } + else -> { + // 모든 검증 통과 시 회원가입 API 호출 + Log.d("LoginScreen", "회원가입 시도: $userId, $userNickname") + userViewModel.createUser(userId, userNickname, password) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors(containerColor = buttonColor), + shape = RoundedCornerShape(10.dp), + enabled = loginState !is UserViewModel.LoginState.Loading + ) { + if (loginState is UserViewModel.LoginState.Loading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color.White + ) + } else { + Text("회원가입", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold) } } - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors(containerColor = buttonColor), - shape = RoundedCornerShape(10.dp), - enabled = loginState !is UserViewModel.LoginState.Loading - ) { - if (loginState is UserViewModel.LoginState.Loading) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - color = Color.White - ) - } else { - Text("회원가입", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold) } } } - } - } } } } From 5ed3f359a520f45025d07204f659a537d02cab1f Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 21:01:41 +0900 Subject: [PATCH 07/15] =?UTF-8?q?=ED=99=88=ED=99=94=EB=A9=B4=20+=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/UserRepository.kt | 22 +++++++++---------- .../playfriends/ui/screen/HomeScreen.kt | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt b/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt index 49c3ea5..cc306d7 100644 --- a/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt +++ b/app/src/main/java/com/example/playfriends/data/repository/UserRepository.kt @@ -13,7 +13,7 @@ import android.util.Log class UserRepository { private val apiService = RetrofitClient.apiService private val gson = Gson() - + // 로그인 suspend fun login(loginRequest: LoginRequest): Result = withContext(Dispatchers.IO) { try { @@ -50,7 +50,7 @@ class UserRepository { Result.failure(Exception(errorMessage)) } } - + // 로그아웃 suspend fun logout(): Result = withContext(Dispatchers.IO) { try { @@ -68,7 +68,7 @@ class UserRepository { Result.failure(e) } } - + // 회원가입 suspend fun createUser(userCreate: UserCreate): Result = withContext(Dispatchers.IO) { try { @@ -95,14 +95,14 @@ class UserRepository { Result.failure(Exception(errorMessage)) } } - + // 에러 응답 파싱 헬퍼 함수 private fun parseErrorResponse(response: retrofit2.Response<*>, statusCode: Int): String { return try { val errorBody = response.errorBody()?.string() if (!errorBody.isNullOrEmpty()) { val errorResponse = gson.fromJson(errorBody, ErrorResponse::class.java) - + // 백엔드에서 보내는 구체적인 에러 메시지 우선 사용 when { errorResponse.detail?.contains("already exists", ignoreCase = true) == true -> "이미 존재하는 ID입니다." @@ -124,7 +124,7 @@ class UserRepository { getDefaultErrorMessage(statusCode) } } - + // 기본 에러 메시지 반환 private fun getDefaultErrorMessage(statusCode: Int): String { return when (statusCode) { @@ -135,7 +135,7 @@ class UserRepository { else -> "회원가입에 실패했습니다. (오류 코드: $statusCode)" } } - + // 현재 사용자 정보 조회 suspend fun getCurrentUser(): Result = withContext(Dispatchers.IO) { try { @@ -154,7 +154,7 @@ class UserRepository { Result.failure(e) } } - + // 사용자의 그룹 목록 조회 suspend fun getUserGroups(userId: String): Result = withContext(Dispatchers.IO) { try { @@ -188,10 +188,10 @@ class UserRepository { Result.success(message) } else { val errorMsg = when { - response.code() == 404 -> "존재하지 않는 그룹입니다." + response.code() == 404 -> "존재하지 않는 초대코드입니다." response.code() == 400 -> "이미 참여 중인 그룹입니다." response.code() == 500 && errorBodyStr.contains("not found", ignoreCase = true) -> "존재하지 않는 그룹입니다." - response.code() == 500 -> "존재하지 않는 그룹입니다." + response.code() == 500 -> "존재하지 않는 초대코드입니다." else -> "그룹 참여에 실패했습니다." } Result.failure(Exception(errorMsg)) @@ -231,5 +231,5 @@ class UserRepository { } } - + } diff --git a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt index cd5797e..553e8b7 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/HomeScreen.kt @@ -245,7 +245,7 @@ fun HomeScreen( colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFD9E9DC)), shape = RoundedCornerShape(8.dp) ) { - Text("Join Group", color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.Medium) + Text("Join Group", color = Color(0xFF4E342E), fontSize = 16.sp, fontWeight = FontWeight.Medium) } Button( onClick = { @@ -263,7 +263,7 @@ fun HomeScreen( colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFD9E9DC)), shape = RoundedCornerShape(8.dp) ) { - Text("Create Group", color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.Medium) + Text("Create Group", color = Color(0xFF4E342E), fontSize = 16.sp, fontWeight = FontWeight.Medium) } } } @@ -271,7 +271,7 @@ fun HomeScreen( onClick = { fabExpanded.value = !fabExpanded.value }, containerColor = Color(0xFF4C6A57) ) { - Icon(Icons.Default.Add, contentDescription = "Add") + Icon(Icons.Default.Add, contentDescription = "Add", tint = Color.White) } } }, @@ -648,7 +648,7 @@ fun HomeScreen( } } Text( - "Join Group 버튼을 누르고 이 초대코드를 입력하면 ${groupName} 그룹에 참여할 수 있습니다.\n각 그룹창에 있는 초대코드를 클릭하면 초대코드를 복사할 수 있습니다.", + "Join Group 버튼을 누르고 이 초대코드를 입력하면 ${groupName} 그룹에 참여할 수 있습니다.\n초대코드는 그룹 창에서도 확인할 수 있습니다.", fontSize = 14.sp, color = Color.Gray, modifier = Modifier.padding(top = 8.dp) From bdad4044a3bee6b6795f70c7f6d22217e5a51313 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 21:28:04 +0900 Subject: [PATCH 08/15] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=97=94=ED=84=B0=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=8F=AC=EC=BB=A4=EC=8A=A4=20=EC=A0=84?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/data/api/RetrofitClient.kt | 2 +- .../playfriends/ui/screen/GroupScreen.kt | 6 +- .../playfriends/ui/screen/LoginScreen.kt | 244 ++++++++++++------ 3 files changed, 168 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/data/api/RetrofitClient.kt b/app/src/main/java/com/example/playfriends/data/api/RetrofitClient.kt index 54f5af9..37cee31 100644 --- a/app/src/main/java/com/example/playfriends/data/api/RetrofitClient.kt +++ b/app/src/main/java/com/example/playfriends/data/api/RetrofitClient.kt @@ -44,7 +44,7 @@ object RetrofitClient { "http://10.0.2.2:8000/" } else { // 실제 기기일 때는 192.249.27.32 사용 - "http://192.249.27.32:8000/" + "https://playfriends-backend-432865170811.us-central1.run.app/" } Log.d("RetrofitClient", "선택된 BASE_URL: $baseUrl") diff --git a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt index 7a60924..b8ddedc 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt @@ -182,7 +182,7 @@ fun GroupScreen( .verticalScroll(scrollState) .padding(start = 30.dp, end = 30.dp, top = 10.dp) ) { - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(11.dp)) // 그룹명/날짜 + 초대코드/위치 (2줄로 분리, 양끝 배치) Column( @@ -251,7 +251,7 @@ fun GroupScreen( } } - Spacer(modifier = Modifier.height(15.dp)) + Spacer(modifier = Modifier.height(10.dp)) // 참여자 카드 group?.let { currentGroup -> @@ -299,7 +299,7 @@ fun GroupScreen( } } - Spacer(modifier = Modifier.height(30.dp)) + Spacer(modifier = Modifier.height(35.dp)) Box(modifier = Modifier.fillMaxWidth().height(1.dp).background(Color(0xFFE0E0E0))) Spacer(modifier = Modifier.height(20.dp)) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt index f3eb0bd..bd65972 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/LoginScreen.kt @@ -31,6 +31,11 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.platform.LocalContext import com.example.playfriends.ui.screen.findActivity import android.widget.Toast +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -68,6 +73,9 @@ fun LoginScreen( // ViewModel 상태 관찰 val loginState by userViewModel.loginState.collectAsState() val user by userViewModel.user.collectAsState() + // FocusRequesters for login + val idFocusRequester = remember { FocusRequester() } + val pwFocusRequester = remember { FocusRequester() } // 사용자 정보 변경 시 로그 출력 LaunchedEffect(user) { @@ -168,14 +176,25 @@ fun LoginScreen( placeholder = { Text("아이디", color = Color.Gray) }, modifier = Modifier .fillMaxWidth() - .height(56.dp), + .height(56.dp) + .focusRequester(idFocusRequester), colors = TextFieldDefaults.outlinedTextFieldColors( unfocusedBorderColor = borderColor, focusedBorderColor = borderColor, containerColor = Color.White ), shape = RoundedCornerShape(8.dp), - singleLine = true + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + if (userId.isBlank()) { + Toast.makeText(context, "아이디를 입력해주세요", Toast.LENGTH_SHORT).show() + } else { + pwFocusRequester.requestFocus() + } + } + ) ) Spacer(modifier = Modifier.height(12.dp)) @@ -187,7 +206,8 @@ fun LoginScreen( placeholder = { Text("비밀번호", color = Color.Gray) }, modifier = Modifier .fillMaxWidth() - .height(56.dp), + .height(56.dp) + .focusRequester(pwFocusRequester), visualTransformation = PasswordVisualTransformation(), colors = TextFieldDefaults.outlinedTextFieldColors( unfocusedBorderColor = borderColor, @@ -195,7 +215,23 @@ fun LoginScreen( containerColor = Color.White ), shape = RoundedCornerShape(8.dp), - singleLine = true + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { + when { + userId.isBlank() -> { + Toast.makeText(context, "아이디를 입력해주세요", Toast.LENGTH_SHORT).show() + } + password.isBlank() -> { + Toast.makeText(context, "비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show() + } + else -> { + userViewModel.login(userId, password) + } + } + } + ) ) Spacer(modifier = Modifier.height(24.dp)) @@ -314,104 +350,152 @@ fun LoginScreen( } } - if (step == 1) { - OutlinedTextField( - value = userNickname, - onValueChange = { userNickname = it }, - placeholder = { Text("닉네임", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true - ) - - Spacer(modifier = Modifier.height(12.dp)) + // FocusRequesters for signup + val nicknameFocusRequester = remember { FocusRequester() } + val idFocusRequester = remember { FocusRequester() } + val pwFocusRequester = remember { FocusRequester() } - OutlinedTextField( - value = userId, - onValueChange = { userId = it }, - placeholder = { Text("아이디", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true + OutlinedTextField( + value = userNickname, + onValueChange = { userNickname = it }, + placeholder = { Text("닉네임", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .focusRequester(nicknameFocusRequester), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + if (userNickname.isBlank()) { + Toast.makeText(context, "닉네임을 입력해주세요", Toast.LENGTH_SHORT).show() + } else { + idFocusRequester.requestFocus() + } + } ) + ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - OutlinedTextField( - value = password, - onValueChange = { password = it }, - placeholder = { Text("비밀번호", color = Color.Gray) }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - visualTransformation = PasswordVisualTransformation(), - colors = TextFieldDefaults.outlinedTextFieldColors( - unfocusedBorderColor = borderColor, - focusedBorderColor = borderColor, - containerColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - singleLine = true + OutlinedTextField( + value = userId, + onValueChange = { userId = it }, + placeholder = { Text("아이디", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .focusRequester(idFocusRequester), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + if (userId.isBlank()) { + Toast.makeText(context, "아이디를 입력해주세요", Toast.LENGTH_SHORT).show() + } else { + pwFocusRequester.requestFocus() + } + } ) + ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(12.dp)) - Button( - onClick = { - // 입력 검증 + OutlinedTextField( + value = password, + onValueChange = { password = it }, + placeholder = { Text("비밀번호", color = Color.Gray) }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .focusRequester(pwFocusRequester), + visualTransformation = PasswordVisualTransformation(), + colors = TextFieldDefaults.outlinedTextFieldColors( + unfocusedBorderColor = borderColor, + focusedBorderColor = borderColor, + containerColor = Color.White + ), + shape = RoundedCornerShape(8.dp), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { + // 회원가입 버튼 클릭과 동일한 로직 실행 when { userNickname.isBlank() -> { - // 닉네임이 비어있으면 에러 메시지 표시 - userViewModel.setError("닉네임을 입력해주세요.") + Toast.makeText(context, "닉네임을 입력해주세요", Toast.LENGTH_SHORT).show() + nicknameFocusRequester.requestFocus() } userId.isBlank() -> { - // 아이디가 비어있으면 에러 메시지 표시 - userViewModel.setError("아이디를 입력해주세요.") + Toast.makeText(context, "아이디를 입력해주세요", Toast.LENGTH_SHORT).show() + idFocusRequester.requestFocus() } userId.length < 3 -> { - // 아이디가 3자 미만이면 에러 메시지 표시 - userViewModel.setError("아이디는 3자 이상으로 입력해주세요.") + Toast.makeText(context, "아이디는 3자 이상으로 입력해주세요.", Toast.LENGTH_SHORT).show() + idFocusRequester.requestFocus() } password.length < 8 -> { - // 비밀번호가 8자 미만이면 에러 메시지 표시 - userViewModel.setError("비밀번호는 8자 이상으로 입력해주세요.") + Toast.makeText(context, "비밀번호는 8자 이상으로 입력해주세요.", Toast.LENGTH_SHORT).show() } else -> { - // 모든 검증 통과 시 회원가입 API 호출 Log.d("LoginScreen", "회원가입 시도: $userId, $userNickname") userViewModel.createUser(userId, userNickname, password) } } - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors(containerColor = buttonColor), - shape = RoundedCornerShape(10.dp), - enabled = loginState !is UserViewModel.LoginState.Loading - ) { - if (loginState is UserViewModel.LoginState.Loading) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - color = Color.White - ) - } else { - Text("회원가입", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold) } + ) + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Button( + onClick = { + // 입력 검증 + when { + userNickname.isBlank() -> { + userViewModel.setError("닉네임을 입력해주세요.") + } + userId.isBlank() -> { + userViewModel.setError("아이디를 입력해주세요.") + } + userId.length < 3 -> { + userViewModel.setError("아이디는 3자 이상으로 입력해주세요.") + } + password.length < 8 -> { + userViewModel.setError("비밀번호는 8자 이상으로 입력해주세요.") + } + else -> { + Log.d("LoginScreen", "회원가입 시도: $userId, $userNickname") + userViewModel.createUser(userId, userNickname, password) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors(containerColor = buttonColor), + shape = RoundedCornerShape(10.dp), + enabled = loginState !is UserViewModel.LoginState.Loading + ) { + if (loginState is UserViewModel.LoginState.Loading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color.White + ) + } else { + Text("회원가입", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold) } } } From bb4ed08091ff0ad4c28898c27ae62e94984ed2c1 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 21:33:40 +0900 Subject: [PATCH 09/15] =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20(=EB=B0=A9=EC=9E=A5)=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/playfriends/ui/screen/GroupScreen.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt index b8ddedc..eab4906 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt @@ -182,7 +182,7 @@ fun GroupScreen( .verticalScroll(scrollState) .padding(start = 30.dp, end = 30.dp, top = 10.dp) ) { - Spacer(modifier = Modifier.height(11.dp)) + Spacer(modifier = Modifier.height(16.dp)) // 그룹명/날짜 + 초대코드/위치 (2줄로 분리, 양끝 배치) Column( @@ -251,7 +251,7 @@ fun GroupScreen( } } - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(15.dp)) // 참여자 카드 group?.let { currentGroup -> @@ -270,7 +270,11 @@ fun GroupScreen( Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.Person, contentDescription = null) Spacer(modifier = Modifier.width(8.dp)) - Text(currentGroup.members.find { it.id == currentGroup.owner_id }?.name ?: "Unknown", fontSize = 16.sp, fontWeight = FontWeight.Bold) + Text( + (currentGroup.members.find { it.id == currentGroup.owner_id }?.name ?: "Unknown") + " (방장)", + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) } IconButton(onClick = { /* TODO: 그룹 나가기 로직 */ }) { Icon(Icons.Default.ExitToApp, contentDescription = "나가기", tint = Color(0xFF942020), modifier = Modifier.size(24.dp)) @@ -299,7 +303,7 @@ fun GroupScreen( } } - Spacer(modifier = Modifier.height(35.dp)) + Spacer(modifier = Modifier.height(30.dp)) Box(modifier = Modifier.fillMaxWidth().height(1.dp).background(Color(0xFFE0E0E0))) Spacer(modifier = Modifier.height(20.dp)) From 438fcc9a803b9d3317d114f39acabee2ee9ffe1b Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 22:10:12 +0900 Subject: [PATCH 10/15] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=B9=AD=ED=98=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/ProfileScreen.kt | 33 +++++++++++++++++-- .../playfriends/ui/screen/TestScreen.kt | 25 ++++---------- .../playfriends/ui/viewmodel/UserViewModel.kt | 7 +++- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt index 536ae55..8289a2b 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt @@ -33,6 +33,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.example.playfriends.ui.viewmodel.UserViewModel +import kotlin.random.Random @Composable fun ProfileScreen( @@ -187,11 +188,37 @@ fun ProfileScreen( // 성향 분석 결과 텍스트 Text("취향 분석 결과,", fontSize = 16.sp) + // 칭호 계산 + val play = user?.play_preferences + val title = remember(play) { + if (play == null) "-" else { + val map = mapOf( + "도파민" to play.vibe_level, + "붐빔" to play.crowd_level, + "활동성" to play.activeness_level, + "유행" to play.trend_level, + "계획성" to play.planning_level, + "실내" to play.location_preference + ) + val max = map.values.maxOrNull() ?: 0f + val maxTypes = map.filter { it.value == max }.keys.toList() + val selected = maxTypes.random() + when (selected) { + "도파민" -> "스릴 헌터" + "붐빔" -> "인간 칵테일" + "활동성" -> "용감한 모험가" + "유행" -> "앞서가는 선구자" + "계획성" -> "전략의 귀재" + "실내" -> "고요한 사색가" + else -> "-" + } + } + } Text( buildAnnotatedString { append("당신은 ") withStyle(SpanStyle(color = Color(0xFF7EA86A), fontWeight = FontWeight.Bold)) { - append("용감한 모험가") + append(title) } append("입니다.") }, @@ -240,7 +267,7 @@ fun ProfileScreen( Spacer(modifier = Modifier.height(32.dp)) } - - } + } +} diff --git a/app/src/main/java/com/example/playfriends/ui/screen/TestScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/TestScreen.kt index 53c563d..f7cd894 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/TestScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/TestScreen.kt @@ -89,18 +89,8 @@ fun TestScreen( // 좌측: 다음에 하기 버튼 Button( onClick = { - // 현재 스택을 확인하여 어디서 왔는지 판단 - if (navController.previousBackStackEntry?.destination?.route == "login") { - // 회원가입에서 온 경우 홈으로 이동 - navController.navigate("home") { - popUpTo("login") { inclusive = true } - } - } else { - // ProfileScreen에서 온 경우 ProfileScreen으로 돌아가기 - navController.navigate("profile") { - popUpTo("test") { inclusive = true } - } - } + // 무조건 ProfileScreen으로 이동 (popUpTo 제거) + navController.navigate("profile") }, modifier = Modifier .align(Alignment.CenterStart) @@ -136,7 +126,6 @@ fun TestScreen( // 우측: 완료 버튼 Button( onClick = { - // 1. FoodPreferences 객체 생성 val foodPrefs = FoodPreferences( ingredients = foodPreferences.filterKeys { it.startsWith("재료_") }.map { (key, value) -> IngredientPreference(FoodIngredient.valueOf(key.substringAfter("재료_")), value.toFloat()) @@ -153,7 +142,6 @@ fun TestScreen( } ) - // 2. PlayPreferences 객체 생성 val playPrefs = PlayPreferences( crowd_level = contentSliderValues["붐비는 정도"] ?: 0f, activeness_level = contentSliderValues["활동성"] ?: 0f, @@ -163,11 +151,10 @@ fun TestScreen( vibe_level = contentSliderValues["분위기"] ?: 0f ) - // 3. ViewModel 함수 호출 - userViewModel.updatePreferences(foodPrefs, playPrefs) - - // 4. 이전 화면으로 돌아가기 - navController.popBackStack() + // updatePreferences에 콜백 전달: 성공 시에만 이동 (popUpTo 제거) + userViewModel.updatePreferences(foodPrefs, playPrefs) { + navController.navigate("profile") + } }, modifier = Modifier .align(Alignment.CenterEnd) diff --git a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt index dfc9156..c3bcbc6 100644 --- a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt @@ -211,7 +211,11 @@ class UserViewModel : ViewModel() { } // 선호도 수정 - fun updatePreferences(foodPreferences: FoodPreferences, playPreferences: PlayPreferences) { + fun updatePreferences( + foodPreferences: FoodPreferences, + playPreferences: PlayPreferences, + onSuccess: (() -> Unit)? = null + ) { viewModelScope.launch { val preferencesUpdate = PreferencesUpdate( food_preferences = foodPreferences, @@ -223,6 +227,7 @@ class UserViewModel : ViewModel() { // 성공 시 사용자 정보 다시 로드하여 UI 갱신 getCurrentUser() Log.d("UserViewModel", "선호도 업데이트 성공") + onSuccess?.invoke() }, onFailure = { exception -> Log.e("UserViewModel", "선호도 업데이트 실패: ${exception.message}") From 80dc9b29edac1d109de66aee671bdada41f5dba8 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 22:18:14 +0900 Subject: [PATCH 11/15] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=EC=8B=9C=20=ED=99=88=EC=9C=BC=EB=A1=9C=20=EB=84=98?= =?UTF-8?q?=EC=96=B4=EA=B0=80=EB=8A=94=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/playfriends/ui/navigation/AuthState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt b/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt index eb49d4c..c17a49c 100644 --- a/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt +++ b/app/src/main/java/com/example/playfriends/ui/navigation/AuthState.kt @@ -21,7 +21,7 @@ fun rememberAuthState(navController: NavController) { val loginState by userViewModel.loginState.collectAsState() LaunchedEffect(user, loginState) { - if (loginState is UserViewModel.LoginState.Success) { + if (loginState is UserViewModel.LoginState.Success && navController.currentDestination?.route == "login") { navController.navigate("home") { popUpTo("splash") { inclusive = true } launchSingleTop = true From 45c71aa5b175ae497d68da831f6ec6e6fab25a5e Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 22:24:05 +0900 Subject: [PATCH 12/15] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B0=92?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EC=97=86=EC=9D=B4=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=20=EB=88=84=EB=A5=BC=20=EB=95=8C=EC=9D=98=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/playfriends/ui/viewmodel/UserViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt index c3bcbc6..2cfc590 100644 --- a/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/example/playfriends/ui/viewmodel/UserViewModel.kt @@ -232,6 +232,7 @@ class UserViewModel : ViewModel() { onFailure = { exception -> Log.e("UserViewModel", "선호도 업데이트 실패: ${exception.message}") // 에러 상태 처리 필요 + onSuccess?.invoke() // 실패해도 콜백 호출 } ) } From cea476e63dc1b17c75eb73567dcd3d5f38185a2b Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 22:33:38 +0900 Subject: [PATCH 13/15] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../playfriends/ui/screen/ProfileScreen.kt | 85 +++++++------------ 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt index 8289a2b..7e26609 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt @@ -74,9 +74,9 @@ fun ProfileScreen( .padding(horizontal = 20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(15.dp)) - Text("Profile", fontSize = 32.sp, fontWeight = FontWeight.Black) + Text("Profile", fontSize = 28.sp, fontWeight = FontWeight.Black) Spacer(modifier = Modifier.height(12.dp)) @@ -84,13 +84,13 @@ fun ProfileScreen( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp), + .padding(horizontal = 32.dp), verticalAlignment = Alignment.CenterVertically ) { // 좌측: 프로필 원형 아이콘 Box( modifier = Modifier - .size(120.dp) + .size(100.dp) .clip(CircleShape) .background(Color(0xFF7E57C2)), contentAlignment = Alignment.Center @@ -111,23 +111,36 @@ fun ProfileScreen( horizontalAlignment = Alignment.End ) { Text( - "닉네임 : ${user?.username ?: "로딩 중..."}", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = Color(0xA9000000) + buildAnnotatedString { + withStyle(SpanStyle(fontSize = 16.sp, fontWeight = FontWeight.Normal, color = Color(0xA9000000))) { + append("닉네임: ") + } + withStyle(SpanStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color(0xA9000000))) { + append(user?.username ?: "로딩 중...") + } + }, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = Color.Unspecified ) Spacer(modifier = Modifier.height(4.dp)) Text( - "아이디 : ${user?.userid ?: "로딩 중..."}", - - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = Color(0xA9000000) + buildAnnotatedString { + withStyle(SpanStyle(fontSize = 16.sp, fontWeight = FontWeight.Normal, color = Color(0xA9000000))) { + append("아이디: ") + } + withStyle(SpanStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color(0xA9000000))) { + append(user?.userid ?: "로딩 중...") + } + }, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = Color.Unspecified ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(4.dp)) Box( modifier = Modifier.clickable { @@ -136,7 +149,7 @@ fun ProfileScreen( ) { Text( "로그아웃", - color = Color(0xFF8B0000), + color = Color(0xFFB21111), fontSize = 14.sp, fontWeight = FontWeight.Bold ) @@ -152,7 +165,7 @@ fun ProfileScreen( colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFA1D0A3)), modifier = Modifier .fillMaxWidth() - .height(48.dp) + .height(35.dp) ) { Icon(Icons.Default.Edit, contentDescription = null, tint = Color.White) Spacer(modifier = Modifier.width(8.dp)) @@ -225,47 +238,7 @@ fun ProfileScreen( fontSize = 18.sp, textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.height(50.dp)) - - // 추천 콘텐츠 카드 - Card( - shape = RoundedCornerShape(16.dp), - elevation = androidx.compose.material3.CardDefaults.cardElevation(defaultElevation = 2.dp), - colors = androidx.compose.material3.CardDefaults.cardColors(containerColor = Color.White) - ) { - Column( - modifier = Modifier.padding(16.dp), - horizontalAlignment = Alignment.Start - ) { - Text( - "⭐ 놀이 콘텐츠 추천 ⭐", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(12.dp)) - - val tags = listOf("놀이공원", "방탈출 카페", "공방", "팝업") - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(tags) { tag -> - Box( - modifier = Modifier - .background(tagColor, RoundedCornerShape(50)) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) { - Text(text = tag, fontSize = 14.sp) - } - } - } - } - } - - Spacer(modifier = Modifier.height(32.dp)) } From 6e5ea6fb5ddc6c374894b1dc7af7bffac41882d5 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 23:59:39 +0900 Subject: [PATCH 14/15] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20UI=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8dfccb9..17f3c74 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,6 +2,6 @@ #FF000000 #FFFFFFFF - #F1FFF4 + #FFFFFF #2B8A3E \ No newline at end of file From 7057a07960e81289c781ab3e84b5fc7abb1ef8b9 Mon Sep 17 00:00:00 2001 From: FloriaO2 Date: Tue, 15 Jul 2025 23:59:58 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20UI=20?= =?UTF-8?q?=EC=B6=94=EC=B6=94=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/playfriends/ui/screen/ProfileScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt index 7e26609..12099ce 100644 --- a/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt +++ b/app/src/main/java/com/example/playfriends/ui/screen/ProfileScreen.kt @@ -169,10 +169,10 @@ fun ProfileScreen( ) { Icon(Icons.Default.Edit, contentDescription = null, tint = Color.White) Spacer(modifier = Modifier.width(8.dp)) - Text("취향 테스트 다시 보러 가기", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Black) + Text("취향 테스트 다시 보러 가기", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Black) } - Spacer(modifier = Modifier.height(30.dp)) + Spacer(modifier = Modifier.height(23.dp)) // 취향 분석 레포트 타이틀 Text( @@ -185,7 +185,7 @@ fun ProfileScreen( fontSize = 18.sp ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(28.dp)) // PlayPreferences를 이용한 육각형 그래프 user?.play_preferences?.let { @@ -238,7 +238,7 @@ fun ProfileScreen( fontSize = 18.sp, textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.height(50.dp)) + }