diff --git a/app/src/main/java/com/gdg/android/presentation/LoginPage.kt b/app/src/main/java/com/gdg/android/presentation/LoginPage.kt index ea73983..893b301 100644 --- a/app/src/main/java/com/gdg/android/presentation/LoginPage.kt +++ b/app/src/main/java/com/gdg/android/presentation/LoginPage.kt @@ -1,8 +1,7 @@ -// LoginPage.kt +// app/src/main/java/com/gdg/android/presentation/LoginPage.kt package com.gdg.android.presentation import android.widget.Toast -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -14,18 +13,22 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController -import kotlinx.coroutines.launch +import bodyLarge +import bodyMedium +import buttontext +import com.gdg.android.ui.theme.BackgroundBox @Composable -fun LoginPage(navController: NavController? = null) { +fun LoginPage( + navController: NavController? = null, + mainViewModel: MainViewModel +) { val context = LocalContext.current val majorTextValue = remember { mutableStateOf("") } @@ -33,91 +36,82 @@ fun LoginPage(navController: NavController? = null) { var majorError by remember { mutableStateOf("") } var nameError by remember { mutableStateOf("") } - Column( - modifier = Modifier - .fillMaxSize() - .background(Color.White) - .padding(30.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Center - ) { - Text( - text = "안녕하세요, 여러분", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Start - ) - - Spacer(modifier = Modifier.height(20.dp)) - - // 학부 입력 - TextFieldSection(label = "학부", textValue = majorTextValue, errorText = majorError) + BackgroundBox { + Column( + modifier = Modifier + .fillMaxSize() + .padding(30.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "안녕하세요, 여러분", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Start + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(20.dp)) - // 이름 입력 - TextFieldSection(label = "이름", textValue = nameTextValue, errorText = nameError) + // 학부 입력 + TextFieldSection(label = "학부", textValue = majorTextValue, errorText = majorError) - Spacer(modifier = Modifier.height(30.dp)) + Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { - majorError = if (majorTextValue.value.isEmpty()) "학부를 입력해주세요." else "" - nameError = if (nameTextValue.value.isEmpty()) "이름을 입력해주세요." else "" + // 이름 입력 + TextFieldSection(label = "이름", textValue = nameTextValue, errorText = nameError) - (context as? MainActivity)?.lifecycleScope?.launch { - (context as? MainActivity)?.saveAutoLoginState(context, true) - } + Spacer(modifier = Modifier.height(30.dp)) - if (majorTextValue.value.isNotEmpty() && nameTextValue.value.isNotEmpty()) { - Toast.makeText(context, "로그인에 성공했습니다", Toast.LENGTH_SHORT).show() - navController?.navigate("profile") { - popUpTo(navController.graph.startDestinationId) { inclusive = true } + Button( + onClick = { + majorError = if (majorTextValue.value.isEmpty()) "학부를 입력해주세요." else "" + nameError = if (nameTextValue.value.isEmpty()) "이름을 입력해주세요." else "" + if (majorTextValue.value.isNotEmpty() && nameTextValue.value.isNotEmpty()) { + Toast.makeText(context, "로그인에 성공했습니다", Toast.LENGTH_SHORT).show() + navController?.let { + it.navigate("main") { + popUpTo(it.graph.startDestinationId) { inclusive = true } + } + mainViewModel.saveAutoLoginState(context, true) + } } - } - }, - modifier = Modifier - .padding(top = 20.dp) - .width(320.dp) - .align(Alignment.CenterHorizontally), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF313131), - contentColor = Color.White - ) - ) { - Text( - text = "LOGIN", - fontWeight = FontWeight.Bold, - fontSize = 16.sp, - style = TextStyle( - letterSpacing = 5.sp + }, + modifier = Modifier + .padding(top = 20.dp) + .width(320.dp) + .align(Alignment.CenterHorizontally), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF313131) ) - ) + ) { + Text( + text = "LOGIN", + style = buttontext + ) + } + + Spacer(modifier = Modifier.height(12.dp)) } } } @Composable fun TextFieldSection(label: String, textValue: MutableState, errorText: String) { - // label에 따라 올바른 조사를 선택 val placeholderText = if (label.endsWith("이름")) "${label}을 입력해주세요" else "${label}를 입력해주세요" Column(modifier = Modifier.fillMaxWidth()) { Text( text = label, - fontSize = 16.sp, - color = Color(0xFF1C1C1C), - modifier = Modifier - .padding(top = 10.dp) - .fillMaxWidth() + style = bodyLarge ) TextField( value = textValue.value, onValueChange = { textValue.value = it }, - label = { Text(placeholderText, color = Color.Black) }, // 변경된 placeholderText 사용 + label = { Text(placeholderText, color = Color.Black) }, colors = TextFieldDefaults.colors( focusedContainerColor = Color(0xFFC4C4C4), unfocusedContainerColor = Color(0xFFE0E0E0), @@ -136,17 +130,17 @@ fun TextFieldSection(label: String, textValue: MutableState, errorText: if (errorText.isNotEmpty()) { Text( text = errorText, - color = Color.Red, - fontSize = 12.sp, - modifier = Modifier.padding(top = 4.dp) + style = bodyMedium, + color = Color.Red ) + } else { + Spacer(modifier = Modifier.height(20.dp)) // 오류 메시지가 없을 때 공간 유지 } } } - @Preview(showBackground = true) @Composable fun LoginPagePreview() { - LoginPage(navController = null) + LoginPage(navController = null, mainViewModel = MainViewModel()) } \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/presentation/MainActivity.kt b/app/src/main/java/com/gdg/android/presentation/MainActivity.kt index e11f699..9269b91 100644 --- a/app/src/main/java/com/gdg/android/presentation/MainActivity.kt +++ b/app/src/main/java/com/gdg/android/presentation/MainActivity.kt @@ -13,6 +13,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -22,47 +23,29 @@ import kotlinx.coroutines.launch val Context.dataStore: DataStore by preferencesDataStore(name = "auto_login") class MainActivity : ComponentActivity() { - - // 키 생성 - private val AUTO_LOGIN_KEY = booleanPreferencesKey("auto_login") - - // 자동 로그인 상태 저장 함수 - suspend fun saveAutoLoginState(context: Context, isLoggedIn: Boolean) { - context.dataStore.edit { preferences -> - preferences[AUTO_LOGIN_KEY] = isLoggedIn - } - } - - // 자동 로그인 상태 불러오기 함수 - fun getAutoLoginState(context: Context): Flow { - return context.dataStore.data - .map { preferences -> - preferences[AUTO_LOGIN_KEY] ?: false // 기본값은 false - } - } - + private lateinit var mainViewModel: MainViewModel // mainViewModel 변수를 미리 생성 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() - lifecycleScope.launch { - val isLoggedIn = getAutoLoginState(applicationContext).first() // 자동 로그인 상태 확인 + mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] // mainViewModel 변수 정의 + + lifecycleScope.launch { + val isLoggedIn = + mainViewModel.getAutoLoginState(applicationContext).first() // 자동 로그인 상태 확인 setContent { val navController = rememberNavController() - - // NavHost 설정 NavHost( navController = navController, startDestination = if (isLoggedIn) "main" else "login" // 자동 로그인 여부에 따라 시작 화면 설정 ) { composable("login") { - LoginPage(navController) + LoginPage(navController, mainViewModel) } - composable("profile") { - ProfileScreen(navController) + composable("main") { + ProfileScreen(navController, mainViewModel) } composable("user") { - UserScreen(navController) + UserScreen(navController, mainViewModel) } composable("userCreate") { UserCreateScreen(navController) diff --git a/app/src/main/java/com/gdg/android/presentation/MainViewModel.kt b/app/src/main/java/com/gdg/android/presentation/MainViewModel.kt index 5ff6c76..3453474 100644 --- a/app/src/main/java/com/gdg/android/presentation/MainViewModel.kt +++ b/app/src/main/java/com/gdg/android/presentation/MainViewModel.kt @@ -1,18 +1,47 @@ package com.gdg.android.presentation +import android.content.Context import android.util.Log +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.gdg.android.api.ServicePool import com.gdg.android.api.User +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class MainViewModel : ViewModel() { private val _users = MutableLiveData>() // 내부에서 수정 가능한 데이터 val users: LiveData> get() = _users // 외부에서 읽기만 가능한 데이터 + val Context.dataStore: DataStore by preferencesDataStore(name = "auto_login") + // 키 생성 + private val AUTO_LOGIN_KEY = booleanPreferencesKey("auto_login") + + // 자동 로그인 상태 저장 함수 + fun saveAutoLoginState(context: Context, isLoggedIn: Boolean) { + viewModelScope.launch { + context.dataStore.edit { preferences -> + preferences[AUTO_LOGIN_KEY] = isLoggedIn + } + } + } + + // 자동 로그인 상태 불러오기 함수 + fun getAutoLoginState(context: Context): Flow { + return context.dataStore.data + .map { preferences -> + preferences[AUTO_LOGIN_KEY] ?: false // 기본값은 false + } + } + fun getUsers() { viewModelScope.launch { runCatching { ServicePool.userService.getUsers(page = 2) } diff --git a/app/src/main/java/com/gdg/android/presentation/ProfileScreen.kt b/app/src/main/java/com/gdg/android/presentation/ProfileScreen.kt index 9a0615f..5a83cce 100644 --- a/app/src/main/java/com/gdg/android/presentation/ProfileScreen.kt +++ b/app/src/main/java/com/gdg/android/presentation/ProfileScreen.kt @@ -15,138 +15,126 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.lifecycleScope import coil.compose.AsyncImage import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController -import kotlinx.coroutines.launch +import com.gdg.android.ui.theme.BackgroundBox +import bodyMedium +import buttontext +import titleLarge val hobbies = listOf( "🍿 혼자 영화 보기", "🛹 스케이트보드 타기", "🎨 그림 그리기", "🎮 게임하기", "📚 독서하기", "💥 만화 보기", "👩‍🍳 요리 하기", "🥁 드럼 연주하기", "🎧 음악 감상하기", "📺 애니 정주행하기" ) - - @Composable -fun ProfileScreen(navController: NavController, name: String = "조영서", major: String = "소프트웨어학부, 2학년") { +fun ProfileScreen(navController: NavController, + mainViewModel: MainViewModel // 파라미터로 mainViewModel 추가 + , name: String = "조영서", major: String = "소프트웨어학부, 2학년") { val context = LocalContext.current - Column( - modifier = Modifier - .fillMaxSize() - .background(Color.White) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top - ) { - AsyncImage( + BackgroundBox { + Column( modifier = Modifier - .padding(top = 30.dp) - .clip(CircleShape) - .size(130.dp), - model = "https://avatars.githubusercontent.com/u/152948170?s=400&u=e554cfdf67c75f47e6ee8ab2680afcbe78fb4292&v=4", - contentDescription = null - ) - - Text( - text = major, - modifier = Modifier.padding(top = 20.dp), - fontSize = 14.sp, - color = Color.Gray, - textAlign = TextAlign.Center - ) - - Text( - text = name, - modifier = Modifier.padding(top = 5.dp), - fontSize = 25.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center - ) - - // Row로 버튼을 감싸 좌우 배치 - Row( - modifier = Modifier.padding(top = 20.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp) // 버튼 간 간격 설정 + .fillMaxSize() + .background(Color.White) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top ) { + AsyncImage( + modifier = Modifier + .padding(top = 30.dp) + .clip(CircleShape) + .size(130.dp), + model = "https://avatars.githubusercontent.com/u/152948170?s=400&u=e554cfdf67c75f47e6ee8ab2680afcbe78fb4292&v=4", + contentDescription = null + ) - Button( - onClick = { navController.navigate("user") }, // 유저 목록 화면으로 이동 - modifier = Modifier.padding(top = 10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF313131), - contentColor = Color.White + Text( + text = major, + style = bodyMedium ) - ) { + Text( - text = "유저 목록", - style = TextStyle( - fontWeight = FontWeight.Bold, - fontSize = 14.sp - ) + text = name, + style = titleLarge ) - } - Button( - onClick = { - (context as? MainActivity)?.lifecycleScope?.launch { - (context as? MainActivity)?.saveAutoLoginState(context, false) + Row( + modifier = Modifier.padding(top = 20.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) // 버튼 간 간격 설정 + ) { + + Button( + onClick = { navController.navigate("user") }, // 유저 목록 화면으로 이동 + modifier = Modifier.padding(top = 10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF313131), + contentColor = Color.White + ) + ) { + Text( + text = "유저 목록", + style = buttontext + ) } - navController.navigate("login") { - popUpTo(navController.graph.startDestinationId) { inclusive = true } + + Spacer(modifier = Modifier.width(10.dp)) + + Button( + onClick = { + mainViewModel.saveAutoLoginState(context, false) // mainViewModel의 saveAutoLoginState() 호출 + navController.navigate("login") { + popUpTo("login") { inclusive = true } + } + }, + modifier = Modifier.padding(top = 10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF313131), + contentColor = Color.White + ) + ) { + Text( + text = "로그아웃", + style = buttontext + ) } - }, - modifier = Modifier.padding(top = 10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF313131), - contentColor = Color.White - ) - ) { - Text( - text = "로그아웃", - style = TextStyle( - fontWeight = FontWeight.Bold, - fontSize = 14.sp - ) - ) - } } + Spacer(modifier = Modifier.height(30.dp)) - Text( - text = "나의 취미", - modifier = Modifier - .padding(top = 25.dp) - .fillMaxWidth(), - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Start - ) + Text( + text = "나의 취미", + style = titleLarge, + textAlign = TextAlign.Start, + modifier = Modifier.fillMaxWidth() // 추가된 부분 + ) - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(top = 10.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(hobbies) { hobby -> - Text( - modifier = Modifier.padding(vertical = 16.dp), - fontSize = 14.sp, - text = hobby - ) - HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = Color.LightGray - ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(top = 10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(hobbies) { hobby -> + Text( + text = hobby, + style = bodyMedium, + modifier = Modifier.fillMaxWidth() + .padding(vertical = 8.dp) + ) + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = Color.LightGray + ) + } } } } @@ -155,5 +143,5 @@ fun ProfileScreen(navController: NavController, name: String = "조영서", majo @Preview(showBackground = true) @Composable fun ProfileScreenPreview() { - ProfileScreen(navController = rememberNavController()) -} + ProfileScreen(navController = rememberNavController(), mainViewModel = MainViewModel()) +} \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/presentation/UserCreateScreen.kt b/app/src/main/java/com/gdg/android/presentation/UserCreateScreen.kt index af47790..94cef93 100644 --- a/app/src/main/java/com/gdg/android/presentation/UserCreateScreen.kt +++ b/app/src/main/java/com/gdg/android/presentation/UserCreateScreen.kt @@ -26,11 +26,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import bodyMedium +import buttontext import com.gdg.android.room.UserDatabase import com.gdg.android.room.UserEntity +import com.gdg.android.ui.theme.BackgroundBox import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import titleLarge @Composable fun UserCreateScreen(navController: NavController) { @@ -40,106 +44,102 @@ fun UserCreateScreen(navController: NavController) { val roomDB = UserDatabase.getDatabase(context) val coroutineScope = rememberCoroutineScope() - Column( - modifier = Modifier - .fillMaxSize() - .background(Color.White) - .padding(40.dp) - ) { - Text( + BackgroundBox { + Column( modifier = Modifier - .fillMaxWidth() - .padding(bottom = 20.dp), - text = "유저 등록하기", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center - ) - Icon( - modifier = Modifier - .fillMaxWidth() - .size(150.dp), - imageVector = Icons.Default.Person, - contentDescription = null, - tint = Color.Gray - ) + .fillMaxSize() + .background(Color.White) + .padding(40.dp) + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 20.dp), + text = "유저 등록하기", + style = titleLarge + ) + Icon( + modifier = Modifier + .fillMaxWidth() + .size(150.dp), + imageVector = Icons.Default.Person, + contentDescription = null, + tint = Color.Gray + ) - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp, bottom = 15.dp), - value = name.value, - onValueChange = { name.value = it }, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color(0xFFC4C4C4), - unfocusedContainerColor = Color(0xFFE0E0E0), - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedTextColor = Color.Black, - unfocusedTextColor = Color.Black, - focusedLabelColor = Color.Black, - unfocusedLabelColor = Color.Black - ), - placeholder = { - Text( - text = "이름을 입력해주세요", - color = Color.Black - ) - } - ) - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 15.dp), - value = email.value, - onValueChange = { email.value = it }, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color(0xFFC4C4C4), - unfocusedContainerColor = Color(0xFFE0E0E0), - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedTextColor = Color.Black, - unfocusedTextColor = Color.Black, - focusedLabelColor = Color.Black, - unfocusedLabelColor = Color.Black - ), - placeholder = { - Text( - text = "이메일을 입력해주세요", - color = Color.Black - ) - } - ) - Spacer(modifier = Modifier.weight(1f)) - Button( - modifier = Modifier - .padding(top = 20.dp) - .width(320.dp) - .align(Alignment.CenterHorizontally), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF313131), - contentColor = Color.White - ), - onClick = { - coroutineScope.launch { - if (name.value.isNotEmpty() && email.value.isNotEmpty()) { - withContext(Dispatchers.IO) { - val newUser = UserEntity(name = name.value, email = email.value) - roomDB.userDao().insert(newUser) // 새로운 유저 데이터 저장 + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp, bottom = 15.dp), + value = name.value, + onValueChange = { name.value = it }, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color(0xFFC4C4C4), + unfocusedContainerColor = Color(0xFFE0E0E0), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedLabelColor = Color.Black, + unfocusedLabelColor = Color.Black + ), + placeholder = { + Text( + text = "이름을 입력해주세요", + style = bodyMedium + ) + } + ) + TextField( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 15.dp), + value = email.value, + onValueChange = { email.value = it }, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color(0xFFC4C4C4), + unfocusedContainerColor = Color(0xFFE0E0E0), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedLabelColor = Color.Black, + unfocusedLabelColor = Color.Black + ), + placeholder = { + Text( + text = "이메일을 입력해주세요", + style = bodyMedium + ) + } + ) + Spacer(modifier = Modifier.weight(1f)) + Button( + modifier = Modifier + .padding(top = 20.dp) + .width(320.dp) + .align(Alignment.CenterHorizontally), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF313131), + contentColor = Color.White + ), + onClick = { + coroutineScope.launch { + if (name.value.isNotEmpty() && email.value.isNotEmpty()) { + withContext(Dispatchers.IO) { + val newUser = UserEntity(name = name.value, email = email.value) + roomDB.userDao().insert(newUser) // 새로운 유저 데이터 저장 + } } } + navController.popBackStack() // 데이터 저장 후 이전 화면으로 되돌아가기 } - navController.popBackStack() // 데이터 저장 후 이전 화면으로 되돌아가기 - } - ) { - Text( - text = "등록하기", - fontWeight = FontWeight.Bold, - fontSize = 16.sp, - style = TextStyle( - letterSpacing = 5.sp + ) { + Text( + text = "등록하기", + style = buttontext ) - ) + } } } } diff --git a/app/src/main/java/com/gdg/android/presentation/UserScreen.kt b/app/src/main/java/com/gdg/android/presentation/UserScreen.kt index 798357d..492cd53 100644 --- a/app/src/main/java/com/gdg/android/presentation/UserScreen.kt +++ b/app/src/main/java/com/gdg/android/presentation/UserScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateListOf @@ -27,22 +28,26 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import coil.compose.AsyncImage import com.gdg.android.api.User import com.gdg.android.room.UserDatabase import com.gdg.android.room.UserEntity +import com.gdg.android.ui.theme.BackgroundBox import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import titleLarge @OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserScreen(navController: NavController) { - val mainViewModel: MainViewModel = viewModel() - val users by mainViewModel.users.observeAsState(emptyList()) +fun UserScreen(navController: NavController, mainViewModel: MainViewModel) { + val userViewModel: UserViewModel = viewModel() + val users by userViewModel.users.collectAsState() val context = LocalContext.current val roomDB = UserDatabase.getDatabase(context) @@ -63,63 +68,65 @@ fun UserScreen(navController: NavController) { mainViewModel.getUsers() } - Scaffold( - topBar = { - TopAppBar( - navigationIcon = { - IconButton(onClick = { navController.popBackStack() }) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = null + BackgroundBox { + Scaffold( + topBar = { + TopAppBar( + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = null + ) + } + }, + title = { + Text( + text = "User List", + style = titleLarge ) - } - }, - title = { - Text( - text = "User List", - fontWeight = FontWeight.Bold - ) - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White) - ) - }, - floatingActionButton = { - SmallFloatingActionButton( - shape = CircleShape, - containerColor = Color.Gray, - contentColor = Color.White, - onClick = { navController.navigate("userCreate") } // Navigate to user create screen - ) { - Icon( - modifier = Modifier.padding(15.dp), - imageVector = Icons.Filled.Edit, - contentDescription = null + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White) ) - } - } - ) { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .background(Color.White) - .padding(innerPadding) - ) { - LazyColumn { - items(users) { user -> - UserItem(user) + }, + floatingActionButton = { + SmallFloatingActionButton( + shape = CircleShape, + containerColor = Color.Gray, + contentColor = Color.White, + onClick = { navController.navigate("userCreate") } // Navigate to user create screen + ) { + Icon( + modifier = Modifier.padding(15.dp), + imageVector = Icons.Filled.Edit, + contentDescription = null + ) } - itemsIndexed(userList) { _, user -> - UserCreateItem( - user = user, - onDeleteClick = { - coroutineScope.launch { - withContext(Dispatchers.IO) { - roomDB.userDao().delete(user) // Delete user + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + .padding(innerPadding) + ) { + LazyColumn { + items(users) { user -> + UserItem(user) + } + itemsIndexed(userList) { _, user -> + UserCreateItem( + user = user, + onDeleteClick = { + coroutineScope.launch { + withContext(Dispatchers.IO) { + roomDB.userDao().delete(user) // Delete user + } + userList.remove(user) // Remove user from UI } - userList.remove(user) // Remove user from UI } - } - ) + ) + } } } } @@ -185,4 +192,10 @@ fun UserCreateItem( thickness = 1.dp, color = Color.LightGray ) +} + +@Preview +@Composable +fun UserScreenPreview() { + UserScreen(rememberNavController(), mainViewModel = MainViewModel()) } \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/presentation/UserViewModel.kt b/app/src/main/java/com/gdg/android/presentation/UserViewModel.kt new file mode 100644 index 0000000..43a11ac --- /dev/null +++ b/app/src/main/java/com/gdg/android/presentation/UserViewModel.kt @@ -0,0 +1,33 @@ +package com.gdg.android.presentation + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.gdg.android.api.ServicePool +import com.gdg.android.api.User +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class UserViewModel : ViewModel() { + private val _users = MutableStateFlow>(emptyList()) + val users: StateFlow> get() = _users + + init { + getUsers() + } + + private fun getUsers() { + viewModelScope.launch { + runCatching { ServicePool.userService.getUsers(page = 2) } + .onSuccess { + _users.value = it.data + Log.d("MainViewModel", "getUsers: ${it.data}") + } + .onFailure { + _users.value = emptyList() + Log.e("MainViewModel", "getUsers: ${it.message}") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/ui/theme/BackgroundBox.kt b/app/src/main/java/com/gdg/android/ui/theme/BackgroundBox.kt new file mode 100644 index 0000000..4425ce8 --- /dev/null +++ b/app/src/main/java/com/gdg/android/ui/theme/BackgroundBox.kt @@ -0,0 +1,23 @@ +package com.gdg.android.ui.theme + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import com.gdg.android.R + +@Composable +fun BackgroundBox(content: @Composable () -> Unit) { + Box(modifier = Modifier.fillMaxSize()) { + Image( + painter = painterResource(id = R.drawable.background), // 배경 이미지 리소스 + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/ui/theme/Color.kt b/app/src/main/java/com/gdg/android/ui/theme/Color.kt index 29be191..475081e 100644 --- a/app/src/main/java/com/gdg/android/ui/theme/Color.kt +++ b/app/src/main/java/com/gdg/android/ui/theme/Color.kt @@ -2,10 +2,10 @@ package com.gdg.android.ui.theme import androidx.compose.ui.graphics.Color -val Purple80 = Color(0xFFD0BCFF) -val PurpleGrey80 = Color(0xFFCCC2DC) -val Pink80 = Color(0xFFEFB8C8) +val LightGray = Color(0xFFB0B0B0) // 밝은 회색 +val MediumGray = Color(0xFF808080) // 중간 회색 +val DarkGray = Color(0xFF505050) // 어두운 회색 -val Purple40 = Color(0xFF6650a4) -val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val DarkerGray = Color(0xFF303030) // 더 어두운 회색 +val VeryDarkGray = Color(0xFF202020) // 매우 어두운 회색 +val AlmostBlack = Color(0xFF101010) // 거의 검정색 \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/ui/theme/Theme.kt b/app/src/main/java/com/gdg/android/ui/theme/Theme.kt index 00614e7..68cc20d 100644 --- a/app/src/main/java/com/gdg/android/ui/theme/Theme.kt +++ b/app/src/main/java/com/gdg/android/ui/theme/Theme.kt @@ -9,34 +9,26 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.material3.Typography import androidx.compose.ui.platform.LocalContext private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 + primary = LightGray, + secondary = MediumGray, + tertiary = DarkGray ) private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + primary = DarkerGray, + secondary = VeryDarkGray, + tertiary = AlmostBlack ) +private val AppTypography = Typography() + @Composable fun GDGAndroidTheme( darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) { @@ -52,7 +44,7 @@ fun GDGAndroidTheme( MaterialTheme( colorScheme = colorScheme, - typography = Typography, + typography = AppTypography, content = content ) } \ No newline at end of file diff --git a/app/src/main/java/com/gdg/android/ui/theme/Type.kt b/app/src/main/java/com/gdg/android/ui/theme/Type.kt index 240e7e3..3240dd6 100644 --- a/app/src/main/java/com/gdg/android/ui/theme/Type.kt +++ b/app/src/main/java/com/gdg/android/ui/theme/Type.kt @@ -1,34 +1,39 @@ -package com.gdg.android.ui.theme - -import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import com.gdg.android.R + +val pretendardBold = FontFamily(Font(R.font.pretendard_bold, FontWeight.Bold)) +val pretendardLight = FontFamily(Font(R.font.pretendard_light, FontWeight.Light)) +val pretendardMedium = FontFamily(Font(R.font.pretendard_medium, FontWeight.Medium)) -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, +val bodyLarge = TextStyle( + fontFamily = pretendardMedium, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, +val bodyMedium = TextStyle( + fontFamily = pretendardLight, fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 20.sp, + letterSpacing = 0.25.sp + ) +val titleLarge = TextStyle( + fontFamily = pretendardBold, + fontWeight = FontWeight.Bold, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp ) - */ -) \ No newline at end of file +val buttontext = TextStyle( + fontFamily = pretendardBold, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + lineHeight = 20.sp, + letterSpacing = 4.sp + ) \ No newline at end of file diff --git a/app/src/main/res/drawable/background.jpeg b/app/src/main/res/drawable/background.jpeg new file mode 100644 index 0000000..f4725a2 Binary files /dev/null and b/app/src/main/res/drawable/background.jpeg differ diff --git a/app/src/main/res/font/pretendard_bold.ttf b/app/src/main/res/font/pretendard_bold.ttf new file mode 100644 index 0000000..fb07fc6 Binary files /dev/null and b/app/src/main/res/font/pretendard_bold.ttf differ diff --git a/app/src/main/res/font/pretendard_light.ttf b/app/src/main/res/font/pretendard_light.ttf new file mode 100644 index 0000000..2e8541d Binary files /dev/null and b/app/src/main/res/font/pretendard_light.ttf differ diff --git a/app/src/main/res/font/pretendard_medium.ttf b/app/src/main/res/font/pretendard_medium.ttf new file mode 100644 index 0000000..1db67c6 Binary files /dev/null and b/app/src/main/res/font/pretendard_medium.ttf differ