Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token> = withContext(Dispatchers.IO) {
try {
Expand Down Expand Up @@ -50,7 +50,7 @@ class UserRepository {
Result.failure(Exception(errorMessage))
}
}

// 로그아웃
suspend fun logout(): Result<Unit> = withContext(Dispatchers.IO) {
try {
Expand All @@ -68,7 +68,7 @@ class UserRepository {
Result.failure(e)
}
}

// 회원가입
suspend fun createUser(userCreate: UserCreate): Result<User> = withContext(Dispatchers.IO) {
try {
Expand All @@ -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입니다."
Expand All @@ -124,7 +124,7 @@ class UserRepository {
getDefaultErrorMessage(statusCode)
}
}

// 기본 에러 메시지 반환
private fun getDefaultErrorMessage(statusCode: Int): String {
return when (statusCode) {
Expand All @@ -135,7 +135,7 @@ class UserRepository {
else -> "회원가입에 실패했습니다. (오류 코드: $statusCode)"
}
}

// 현재 사용자 정보 조회
suspend fun getCurrentUser(): Result<User> = withContext(Dispatchers.IO) {
try {
Expand All @@ -154,7 +154,7 @@ class UserRepository {
Result.failure(e)
}
}

// 사용자의 그룹 목록 조회
suspend fun getUserGroups(userId: String): Result<GroupList> = withContext(Dispatchers.IO) {
try {
Expand Down Expand Up @@ -188,16 +188,16 @@ 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))
}
} catch (e: Exception) {
Result.failure(Exception("그룹 참여 중 오류가 발생했습니다: ${e.message}"))
Result.failure(Exception("그룹 참여 중 오류가 발생했습니다."))
}
}

Expand Down Expand Up @@ -231,5 +231,5 @@ class UserRepository {
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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.currentDestination?.route == "login") {
navController.navigate("home") {
popUpTo("splash") { inclusive = true }
launchSingleTop = true
Expand Down
160 changes: 139 additions & 21 deletions app/src/main/java/com/example/playfriends/ui/screen/GroupScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,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(
Expand Down Expand Up @@ -80,6 +120,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(Unit) {
groupViewModel.getGroup(groupId)
userViewModel.getCurrentUser()
Expand Down Expand Up @@ -114,7 +162,12 @@ fun GroupScreen(
profileInitial = currentUser?.username?.first()?.toString() ?: "A"
)
},
containerColor = backgroundColor
containerColor = backgroundColor,
snackbarHost = {
SnackbarHost(hostState = snackbarHostState) { data ->
CustomSnackbar(message = data.visuals.message)
}
}
) { padding ->
val currentGroup = group
val user = currentUser
Expand All @@ -128,38 +181,95 @@ fun GroupScreen(
val isOwner = currentGroup.owner_id == user._id
Log.d("GroupScreen", "isOwner: $isOwner, currentUser: ${user._id}, groupOwner: ${currentGroup.owner_id}")

// 스낵바 상태 감지해서 띄우기
if (showSnackbar) {
LaunchedEffect(snackbarMsg) {
snackbarHostState.showSnackbar(snackbarMsg)
showSnackbar = false
}
}

val isOwner = group?.owner_id == currentUser?._id

Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
.verticalScroll(scrollState)
.padding(start = 30.dp, end = 30.dp, top = 10.dp)
) {
Spacer(modifier = Modifier.height(16.dp))

// 그룹명/날짜 + 초대코드/위치 (2줄로 분리, 양끝 배치)
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
.verticalScroll(scrollState)
.padding(horizontal = 24.dp)
.fillMaxWidth()
.padding(horizontal = 0.dp)
) {
Spacer(modifier = Modifier.height(16.dp))

// 그룹명 + 그룹코드 + 시간/위치
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
.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)
)
}
// 하단: 초대코드 - 위치
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()

.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(horizontalAlignment = Alignment.Start) {
Text(currentGroup.groupname, fontSize = 26.sp, fontWeight = FontWeight.Bold, color = titleColor)
Text(currentGroup._id, fontSize = 12.sp, color = Color.Gray) // 그룹 코드를 ID로 임시 대체
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)
}
Column(horizontalAlignment = Alignment.End) {
Text(currentGroup.starttime, fontSize = 16.sp, color = Color.Black) // 시간 포맷팅 필요
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) // 위치 정보 필요
}
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)

}
}

Spacer(modifier = Modifier.height(12.dp))

Spacer(modifier = Modifier.height(15.dp))


// 참여자 카드
Card(
Expand All @@ -177,7 +287,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))
Expand Down Expand Up @@ -382,8 +496,12 @@ fun OwnerScheduleCreationUI(
) {
Text("취소")
}
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))
Expand Down
Loading