diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 3909897..f46d244 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -6,7 +6,7 @@
-
+
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/src/main/java/com/khigh/seniormap/ui/composables/navigation/AppNavigation.kt b/app/src/main/java/com/khigh/seniormap/ui/composables/navigation/AppNavigation.kt
index 08bec45..6f763b6 100644
--- a/app/src/main/java/com/khigh/seniormap/ui/composables/navigation/AppNavigation.kt
+++ b/app/src/main/java/com/khigh/seniormap/ui/composables/navigation/AppNavigation.kt
@@ -10,9 +10,13 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
import com.khigh.seniormap.ui.screens.HomeScreen
import com.khigh.seniormap.ui.screens.LoadingScreen
import com.khigh.seniormap.ui.screens.LoginScreen
+import com.khigh.seniormap.ui.screens.guardian.GuardianHomeScreen
+import com.khigh.seniormap.ui.screens.guardian.EditGuardianScreen
import com.khigh.seniormap.viewmodel.AuthViewModel
import androidx.hilt.navigation.compose.hiltViewModel
@@ -56,20 +60,65 @@ fun AppNavigation(
Log.d("com.khigh.seniormap", "[AppNavigation] isAuthenticated: $isAuthenticated, authState: $authState")
NavHost(
navController = navController,
- startDestination = if (isAuthenticated) "home" else "login"
+ startDestination = if (isAuthenticated) "guardian_home" else "login"
) {
// 로그인 화면
composable("login") {
LoginScreen(
onNavigateToHome = {
- navController.navigate("home") {
+ navController.navigate("guardian_home") {
popUpTo("login") { inclusive = true }
}
}
)
}
- // 홈 화면
+ // 보호인 홈 화면
+ composable("guardian_home") {
+ GuardianHomeScreen(
+ onNavigateToLogin = {
+ navController.navigate("login") {
+ popUpTo("guardian_home") { inclusive = true }
+ }
+ },
+ onNavigateToEdit = { guardianData ->
+ navController.navigate("edit_guardian/${guardianData.id}") {
+ // 뒤로 가기 시 홈 화면으로 돌아감
+ }
+ }
+ )
+ }
+
+ // 피보호인 수정 화면
+ composable(
+ route = "edit_guardian/{guardianId}",
+ arguments = listOf(
+ navArgument("guardianId") { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+ val guardianId = backStackEntry.arguments?.getString("guardianId") ?: ""
+ // TODO: 실제로는 ViewModel에서 guardianId로 데이터를 가져와야 함
+ val guardianData = com.khigh.seniormap.ui.screens.guardian.components.GuardianData(
+ id = guardianId,
+ name = "임시 피보호인",
+ location = "임시 위치",
+ isAtHome = true
+ )
+
+ EditGuardianScreen(
+ guardianData = guardianData,
+ onNavigateBack = {
+ navController.popBackStack()
+ },
+ onSave = { updatedGuardian ->
+ // TODO: 실제로는 ViewModel을 통해 데이터를 저장해야 함
+ Log.d("AppNavigation", "Guardian updated: $updatedGuardian")
+ navController.popBackStack()
+ }
+ )
+ }
+
+ // 기존 홈 화면 (필요시 유지)
composable("home") {
HomeScreen(
onNavigateToLogin = {
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/LoginScreen.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/LoginScreen.kt
index 4f11ba7..e7e595f 100644
--- a/app/src/main/java/com/khigh/seniormap/ui/screens/LoginScreen.kt
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/LoginScreen.kt
@@ -124,6 +124,10 @@ fun LoginScreen(
onGoogleLogin = {
Log.d("com.khigh.seniormap", "[LoginScreen] Google login clicked")
authViewModel.loginWithOAuth(Google)
+ },
+ onTemporaryGuardianHome = {
+ Log.d("com.khigh.seniormap", "[LoginScreen] Temporary guardian home clicked")
+ onNavigateToHome()
}
)
}
@@ -163,6 +167,7 @@ fun LoginScreen(
private fun LoginButtonsSection(
onKakaoLogin: () -> Unit,
onGoogleLogin: () -> Unit,
+ onTemporaryGuardianHome: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
@@ -214,5 +219,18 @@ private fun LoginButtonsSection(
fontWeight = FontWeight.Medium
)
}
+
+ // 임시 보호인 홈 이동 버튼
+ Spacer(modifier = Modifier.height(24.dp))
+ TextButton(
+ onClick = onTemporaryGuardianHome,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = "(임시) 보호인 홈 넘어가기",
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/EditGuardianScreen.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/EditGuardianScreen.kt
new file mode 100644
index 0000000..bafcc42
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/EditGuardianScreen.kt
@@ -0,0 +1,261 @@
+package com.khigh.seniormap.ui.screens.guardian
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import com.khigh.seniormap.ui.screens.guardian.components.GuardianData
+
+/**
+ * 피보호인 정보 수정 화면
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun EditGuardianScreen(
+ guardianData: GuardianData,
+ onNavigateBack: () -> Unit = {},
+ onSave: (GuardianData) -> Unit = {},
+ modifier: Modifier = Modifier
+) {
+ // 수정할 데이터 상태
+ var name by remember { mutableStateOf(guardianData.name) }
+ var location by remember { mutableStateOf(guardianData.location) }
+ var phoneNumber by remember { mutableStateOf("010-1234-5678") } // 임시 데이터
+ var emergencyContact by remember { mutableStateOf("010-9876-5432") } // 임시 데이터
+ var address by remember { mutableStateOf("서울시 강남구 테헤란로 123") } // 임시 데이터
+ var notes by remember { mutableStateOf("특별한 주의사항이 있습니다.") } // 임시 데이터
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("피보호인 수정") },
+ navigationIcon = {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.Default.ArrowBack,
+ contentDescription = "뒤로 가기"
+ )
+ }
+ },
+ actions = {
+ TextButton(
+ onClick = {
+ // 수정된 데이터로 저장
+ val updatedGuardian = guardianData.copy(
+ name = name,
+ location = location
+ )
+ onSave(updatedGuardian)
+ onNavigateBack()
+ }
+ ) {
+ Text(
+ text = "저장",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Medium
+ )
+ }
+ }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .verticalScroll(rememberScrollState())
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ // 프로필 이미지 섹션
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Box(
+ modifier = Modifier
+ .size(100.dp)
+ .clip(CircleShape)
+ .clickable { /* TODO: 이미지 선택 */ },
+ contentAlignment = Alignment.Center
+ ) {
+ if (guardianData.profileImageRes != null) {
+ Image(
+ painter = painterResource(id = guardianData.profileImageRes),
+ contentDescription = "프로필 이미지",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ // 기본 프로필 아이콘
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ shape = CircleShape,
+ color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Edit,
+ contentDescription = "프로필 이미지 선택",
+ modifier = Modifier
+ .size(40.dp)
+ .padding(20.dp),
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "프로필 이미지 선택",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
+
+ // 기본 정보 섹션
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ ),
+ elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Text(
+ text = "기본 정보",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
+
+ EditField(
+ label = "이름",
+ value = name,
+ onValueChange = { name = it }
+ )
+
+ EditField(
+ label = "현재 위치",
+ value = location,
+ onValueChange = { location = it }
+ )
+
+ EditField(
+ label = "연락처",
+ value = phoneNumber,
+ keyboardType = KeyboardType.Phone,
+ onValueChange = { phoneNumber = it }
+ )
+ }
+ }
+
+ // 비상 연락처 섹션
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ ),
+ elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Text(
+ text = "비상 연락처",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
+
+ EditField(
+ label = "비상 연락처",
+ value = emergencyContact,
+ keyboardType = KeyboardType.Phone,
+ onValueChange = { emergencyContact = it }
+ )
+
+ EditField(
+ label = "주소",
+ value = address,
+ onValueChange = { address = it }
+ )
+ }
+ }
+
+ // 기타 정보 섹션
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ ),
+ elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Text(
+ text = "기타 정보",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
+
+ EditField(
+ label = "특이사항",
+ value = notes,
+ onValueChange = { notes = it }
+ )
+ }
+ }
+
+ // 하단 여백
+ Spacer(modifier = Modifier.height(32.dp))
+ }
+ }
+}
+
+/**
+ * 편집 가능한 입력 필드 컴포넌트
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun EditField(
+ label: String,
+ value: String,
+ keyboardType: KeyboardType = KeyboardType.Text,
+ onValueChange: (String) -> Unit
+) {
+ OutlinedTextField(
+ value = value,
+ onValueChange = onValueChange,
+ label = { Text(label) },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
+ keyboardType = keyboardType
+ ),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = MaterialTheme.colorScheme.primary,
+ unfocusedBorderColor = MaterialTheme.colorScheme.outline
+ )
+ )
+}
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/GuardianHomeScreen.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/GuardianHomeScreen.kt
new file mode 100644
index 0000000..4083072
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/GuardianHomeScreen.kt
@@ -0,0 +1,141 @@
+package com.khigh.seniormap.ui.screens.guardian
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.khigh.seniormap.ui.screens.guardian.components.*
+
+/**
+ * 보호인 홈 화면 컴포넌트
+ *
+ * 보호인이 피보호인들의 현재 상태와 위치를 확인할 수 있는 메인 화면입니다.
+ * 피보호인 목록과 각각의 상태 정보를 표시하며, 하단 네비게이션을 통해
+ * 다른 기능들로 이동할 수 있습니다.
+ *
+ * @param modifier 레이아웃 수정자
+ * @param onNavigateToLogin 로그인 화면으로 이동하는 콜백
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun GuardianHomeScreen(
+ modifier: Modifier = Modifier,
+ onNavigateToLogin: () -> Unit = {},
+ onNavigateToEdit: (GuardianData) -> Unit = {}
+) {
+ // 수정 모드 상태 추가
+ var isEditMode by remember { mutableStateOf(false) }
+ // 임시 데이터 - 실제로는 ViewModel에서 가져올 데이터
+ val guardians = remember {
+ listOf(
+ GuardianData(
+ id = "1",
+ name = "김할자",
+ location = "집",
+ isAtHome = true
+ ),
+ GuardianData(
+ id = "2",
+ name = "송진호",
+ location = "마을",
+ isAtHome = false
+ ),
+ GuardianData(
+ id = "3",
+ name = "나문희",
+ location = "집",
+ isAtHome = true
+ ),
+ GuardianData(
+ id = "4",
+ name = "최불암",
+ location = "마을",
+ isAtHome = false
+ )
+ )
+ }
+
+ // 현재 선택된 하단 네비게이션 탭
+ var selectedTab by remember { mutableIntStateOf(0) }
+
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ // 수정된 헤더 (수정 버튼 추가)
+ GuardianHeader(
+ title = "나의 피보호인",
+ isEditMode = isEditMode,
+ onEditClick = {
+ isEditMode = !isEditMode // 수정 모드 토글
+ },
+ onAddClick = {
+ // TODO: 피보호인 추가 화면으로 이동
+ }
+ )
+ },
+ bottomBar = {
+ // 하단 네비게이션 바
+ BottomNavigationBar(
+ selectedTab = selectedTab,
+ onTabSelected = { tabIndex ->
+ selectedTab = tabIndex
+ // TODO: 각 탭에 따른 화면 전환 로직
+ }
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ when (selectedTab) {
+ 0 -> {
+ // 홈 탭 - 피보호인 목록 (수정 모드 전달)
+ GuardianList(
+ guardians = guardians,
+ isEditMode = isEditMode,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 8.dp),
+ onGuardianClick = { guardian ->
+ // 일반 모드에서만 상세 화면 이동
+ if (!isEditMode) {
+ // TODO: 피보호인 상세 화면으로 이동
+ }
+ },
+ onGuardianEdit = { guardian ->
+ // 수정 화면으로 이동
+ onNavigateToEdit(guardian)
+ }
+ )
+ }
+ 1 -> {
+ // 지도 탭 - 지도 화면 (임시)
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = androidx.compose.ui.Alignment.Center
+ ) {
+ Text(
+ text = "지도 화면 (구현 예정)",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ 2 -> {
+ // 설정 탭 - 설정 화면 (임시)
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = androidx.compose.ui.Alignment.Center
+ ) {
+ Text(
+ text = "설정 화면 (구현 예정)",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/BottomNavigationBar.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/BottomNavigationBar.kt
new file mode 100644
index 0000000..b163802
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/BottomNavigationBar.kt
@@ -0,0 +1,114 @@
+package com.khigh.seniormap.ui.screens.guardian.components
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.List
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.material.icons.outlined.List
+import androidx.compose.material.icons.outlined.Search
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+
+/**
+ * 하단 네비게이션 바 컴포넌트
+ *
+ * @param selectedTab 현재 선택된 탭
+ * @param modifier 레이아웃 수정자
+ * @param onTabSelected 탭 선택 이벤트 콜백
+ */
+@Composable
+fun BottomNavigationBar(
+ selectedTab: Int = 0,
+ modifier: Modifier = Modifier,
+ onTabSelected: (Int) -> Unit = {}
+) {
+ NavigationBar(
+ modifier = modifier,
+ containerColor = MaterialTheme.colorScheme.surface,
+ contentColor = MaterialTheme.colorScheme.onSurface
+ ) {
+ // 홈 탭
+ NavigationBarItem(
+ icon = {
+ Icon(
+ imageVector = if (selectedTab == 0) Icons.Filled.Home else Icons.Outlined.Home,
+ contentDescription = "홈"
+ )
+ },
+ label = {
+ Text(
+ text = "홈",
+ style = MaterialTheme.typography.labelSmall,
+ fontWeight = if (selectedTab == 0) FontWeight.Bold else FontWeight.Normal
+ )
+ },
+ selected = selectedTab == 0,
+ onClick = { onTabSelected(0) },
+ colors = NavigationBarItemDefaults.colors(
+ selectedIconColor = MaterialTheme.colorScheme.primary,
+ selectedTextColor = MaterialTheme.colorScheme.primary,
+ unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ indicatorColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
+ )
+ )
+
+ // 지도 탭
+ NavigationBarItem(
+ icon = {
+ Icon(
+ imageVector = if (selectedTab == 1) Icons.Filled.Search else Icons.Outlined.Search,
+ contentDescription = "지도"
+ )
+ },
+ label = {
+ Text(
+ text = "지도",
+ style = MaterialTheme.typography.labelSmall,
+ fontWeight = if (selectedTab == 1) FontWeight.Bold else FontWeight.Normal
+ )
+ },
+ selected = selectedTab == 1,
+ onClick = { onTabSelected(1) },
+ colors = NavigationBarItemDefaults.colors(
+ selectedIconColor = MaterialTheme.colorScheme.primary,
+ selectedTextColor = MaterialTheme.colorScheme.primary,
+ unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ indicatorColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
+ )
+ )
+
+ // 설정 탭
+ NavigationBarItem(
+ icon = {
+ Icon(
+ imageVector = if (selectedTab == 2) Icons.Filled.List else Icons.Outlined.List,
+ contentDescription = "설정"
+ )
+ },
+ label = {
+ Text(
+ text = "설정",
+ style = MaterialTheme.typography.labelSmall,
+ fontWeight = if (selectedTab == 2) FontWeight.Bold else FontWeight.Normal
+ )
+ },
+ selected = selectedTab == 2,
+ onClick = { onTabSelected(2) },
+ colors = NavigationBarItemDefaults.colors(
+ selectedIconColor = MaterialTheme.colorScheme.primary,
+ selectedTextColor = MaterialTheme.colorScheme.primary,
+ unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ indicatorColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianData.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianData.kt
new file mode 100644
index 0000000..7d3cd21
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianData.kt
@@ -0,0 +1,12 @@
+package com.khigh.seniormap.ui.screens.guardian.components
+
+/**
+ * 보호인 목록 데이터 클래스
+ */
+data class GuardianData(
+ val id: String,
+ val name: String,
+ val location: String,
+ val profileImageRes: Int? = null,
+ val isAtHome: Boolean = true
+)
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianHeader.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianHeader.kt
new file mode 100644
index 0000000..947da3a
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianHeader.kt
@@ -0,0 +1,83 @@
+package com.khigh.seniormap.ui.screens.guardian.components
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+
+/**
+ * 보호인 홈 화면 헤더 컴포넌트
+ *
+ * @param title 헤더 제목
+ * @param isEditMode 수정 모드 여부
+ * @param modifier 레이아웃 수정자
+ * @param onEditClick 수정 버튼 클릭 이벤트 콜백
+ * @param onAddClick 추가 버튼 클릭 이벤트 콜백
+ */
+@Composable
+fun GuardianHeader(
+ title: String = "나의 피보호인",
+ isEditMode: Boolean = false,
+ modifier: Modifier = Modifier,
+ onEditClick: () -> Unit = {},
+ onAddClick: () -> Unit = {}
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 8.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 제목
+ Text(
+ text = title,
+ style = MaterialTheme.typography.headlineMedium,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+
+ // 수정 버튼과 추가 버튼
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 수정 버튼
+ TextButton(
+ onClick = onEditClick,
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = if (isEditMode)
+ MaterialTheme.colorScheme.primary
+ else
+ MaterialTheme.colorScheme.onSurface
+ )
+ ) {
+ Text(
+ text = if (isEditMode) "완료" else "수정",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Medium
+ )
+ }
+
+ // 추가 버튼
+ IconButton(
+ onClick = onAddClick,
+ colors = IconButtonDefaults.iconButtonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "피보호인 추가",
+ modifier = Modifier.size(20.dp)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianList.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianList.kt
new file mode 100644
index 0000000..f1877c1
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianList.kt
@@ -0,0 +1,49 @@
+package com.khigh.seniormap.ui.screens.guardian.components
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.LocationOn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * 보호인 목록 컴포넌트
+ *
+ * @param guardians 보호인 목록 데이터
+ * @param isEditMode 수정 모드 여부
+ * @param modifier 레이아웃 수정자
+ * @param onGuardianClick 보호인 클릭 이벤트 콜백
+ * @param onGuardianEdit 보호인 수정 이벤트 콜백
+ */
+@Composable
+fun GuardianList(
+ guardians: List,
+ isEditMode: Boolean = false,
+ modifier: Modifier = Modifier,
+ onGuardianClick: (GuardianData) -> Unit = {},
+ onGuardianEdit: (GuardianData) -> Unit = {}
+) {
+ LazyColumn(
+ modifier = modifier,
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(guardians) { guardian ->
+ GuardianListItem(
+ name = guardian.name,
+ location = guardian.location,
+ profileImageRes = guardian.profileImageRes,
+ statusIcon = if (guardian.isAtHome) Icons.Default.Home else Icons.Default.LocationOn,
+ statusColor = if (guardian.isAtHome) Color(0xFF4CAF50) else Color(0xFFFF9800),
+ isEditMode = isEditMode,
+ onClick = { onGuardianClick(guardian) },
+ onEditClick = { onGuardianEdit(guardian) }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianListItem.kt b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianListItem.kt
new file mode 100644
index 0000000..752fc6a
--- /dev/null
+++ b/app/src/main/java/com/khigh/seniormap/ui/screens/guardian/components/GuardianListItem.kt
@@ -0,0 +1,143 @@
+package com.khigh.seniormap.ui.screens.guardian.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.LocationOn
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+
+/**
+ * 보호인 목록 아이템 컴포넌트
+ *
+ * @param name 보호인 이름
+ * @param location 현재 위치
+ * @param profileImageRes 프로필 이미지 리소스 ID (null이면 기본 아이콘 사용)
+ * @param statusIcon 상태 아이콘
+ * @param statusColor 상태 색상
+ * @param isEditMode 수정 모드 여부
+ * @param modifier 레이아웃 수정자
+ * @param onClick 클릭 이벤트 콜백
+ * @param onEditClick 수정 버튼 클릭 이벤트 콜백
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun GuardianListItem(
+ name: String,
+ location: String,
+ profileImageRes: Int? = null,
+ statusIcon: ImageVector = Icons.Default.Home,
+ statusColor: Color = Color(0xFF4CAF50),
+ isEditMode: Boolean = false,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ onEditClick: () -> Unit = {}
+) {
+ Card(
+ onClick = if (!isEditMode) onClick else { {} },
+ modifier = modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ ),
+ elevation = CardDefaults.cardElevation(
+ defaultElevation = 2.dp
+ )
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 프로필 이미지 또는 기본 아이콘
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ if (profileImageRes != null) {
+ Image(
+ painter = painterResource(id = profileImageRes),
+ contentDescription = "$name 프로필",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ // 기본 프로필 아이콘
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ shape = CircleShape,
+ color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
+ ) {
+ Icon(
+ imageVector = Icons.Default.LocationOn,
+ contentDescription = "기본 프로필",
+ modifier = Modifier
+ .size(24.dp)
+ .padding(12.dp),
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ // 이름과 위치 정보
+ Column(
+ modifier = Modifier.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(
+ text = name,
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Medium,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ Text(
+ text = location,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+
+ // 수정 모드일 때 수정 버튼 표시
+ if (isEditMode) {
+ IconButton(
+ onClick = onEditClick,
+ colors = IconButtonDefaults.iconButtonColors(
+ containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
+ contentColor = MaterialTheme.colorScheme.primary
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Default.Edit,
+ contentDescription = "수정",
+ modifier = Modifier.size(20.dp)
+ )
+ }
+ } else {
+ // 일반 모드일 때 상태 아이콘
+ Icon(
+ imageVector = statusIcon,
+ contentDescription = "상태",
+ modifier = Modifier.size(24.dp),
+ tint = statusColor
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 20e2a01..b6b3ddd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,7 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+
+# 한글 경로 문제 해결 (non-ASCII characters)
+android.overridePathCheck=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8d8ce30..592b479 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,7 @@
[versions]
-agp = "8.12.0"
+#agp = "8.12.0"
+agp = "8.11.1"
+
kotlin = "2.2.10"
coreKtx = "1.16.0"
junit = "4.13.2"
@@ -98,6 +100,7 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
+android-library = { id = "com.android.library", version.ref = "agp" } # 새로추가함
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }