오랜만에 다시 시작 했는데??!!
우선 gradle, studio를 최신으로 업데이트 후 Version Catalog 버전을 최신화 했다.
git에서 pull을 받아서 GardenActivity를 켯는데 뭔가 변했다.
여기부터 다시 해야할 듯 하다.
변경 전
class GardenActivity : AppCompatActivity() {
private val viewModel: PlantListViewModel by viewModels()
private val menuProvider = object :MenuProvider{
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_plant_list,menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when(menuItem.itemId){
R.id.filter_zone -> {
viewModel.updateData()
true
}
else -> false
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MdcTheme{
SunflowerApp(
onAttached = { toolbar ->
setSupportActionBar(toolbar)
},
onPageChange = { page ->
when(page){
SunflowerPage.MY_GARDEN -> removeMenuProvider(menuProvider)
SunflowerPage.PLANT_LIST -> addMenuProvider(menuProvider, this)
}
},
plantListViewModel = viewModel
)
}
}
}
}
변경 후
class GardenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
SunFlowerTheme {
SunflowerApp()
}
}
}
}
MdcTheme에서 SunFlowerTheme로 바뀌면서 Viewmodel, Provider 코드가 사라지고, SunflowerApp 변수가 사라졌다.
SunFlowerTheme도 변한게 있을까 싶어서 들어가서 확인해보니 달라졌다!!
변경 전
fun SunFlowerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
변경 후
fun SunFlowerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = !isSystemInDarkTheme()
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
DisposableEffect(systemUiController, useDarkIcons ){
systemUiController.setSystemBarsColor(color = Color.Transparent, darkIcons = useDarkIcons)
onDispose { }
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
val Shapes = Shapes(
small = RoundedCornerShape(
topStart = 0.dp,
topEnd = 12.dp,
bottomStart = 12.dp,
bottomEnd = 0.dp
),
medium = RoundedCornerShape(
topStart = 0.dp,
topEnd = 12.dp,
bottomStart = 12.dp,
bottomEnd = 0.dp
)
)
fun SunflowerApp(
onPageChange : (SunflowerPage) -> Unit = {},
onAttached: (Toolbar) -> Unit = {},
plantListViewModel : PlantListViewModel = hiltViewModel()
){
val navController = rememberNavController()
SunFlowerNavHost(
plantListViewModel = plantListViewModel,
navController = navController,
onPageChange = onPageChange,
onAttached = onAttached
)
}
변경 후
fun SunflowerApp(){
val navController = rememberNavController()
SunFlowerNavHost(
navController = navController
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HomePagerScreen(
onPlantClick: (Plant) -> Unit,
pagerState: PagerState,
modifier: Modifier = Modifier,
pages: Array<SunflowerPage> = SunflowerPage.values()
){
Column(modifier) {
val coroutineScope = rememberCoroutineScope()
TabRow(selectedTabIndex = pagerState.currentPage) {
pages.forEachIndexed{index, page ->
val title = stringResource(id = page.titleResId)
Tab(
selected = pagerState.currentPage == index,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) }},
text = { Text(text = title)},
icon = {
Icon(painter = painterResource(id = page.drawableResId), contentDescription = title)
},
unselectedContentColor = MaterialTheme.colorScheme.secondary
)
}
}
HorizontalPager(
modifier = Modifier.background(MaterialTheme.colorScheme.background),
pageCount = pages.size,
state = pagerState,
verticalAlignment = Alignment.Top
) { index ->
when (pages[index]) {
SunflowerPage.MY_GARDEN -> {
GardenScreen(
Modifier.fillMaxSize(),
onAddPlantClick = {
coroutineScope.launch {
pagerState.scrollToPage(SunflowerPage.PLANT_LIST.ordinal)
}
},
onPlantClick = {
onPlantClick(it.plant)
})
}
SunflowerPage.PLANT_LIST -> {
PlantListScreen(
onPlantClick = onPlantClick,
modifier = Modifier.fillMaxSize(),
)
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
private fun HomeTopAppBar(
pagerState: PagerState,
onFilterClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier
) {
TopAppBar(
title = {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.displaySmall
)
}
},
modifier = modifier.statusBarsPadding(),
actions = {
if (pagerState.currentPage == SunflowerPage.PLANT_LIST.ordinal) {
IconButton(onClick = onFilterClick) {
Icon(
painter = painterResource(id = R.drawable.ic_filter_list_24dp),
contentDescription = stringResource(
id = R.string.menu_filter_by_grow_zone
)
)
}
}
},
scrollBehavior = scrollBehavior
)
}
val plants: LiveData<List<Plant>> = growZone.flatMapLatest { zone ->
if (zone == NO_GROW_ZONE) {
plantRepository.getPlants()
} else {
plantRepository.getPlantsWithGrowZoneNumber(zone)
}
}.asLiveData()
fun PlantListScreen(
plants: List<Plant>,
modifier: Modifier = Modifier,
onPlantClick: (Plant) -> Unit = {},
) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = modifier.testTag("plant_list"),
contentPadding = PaddingValues(
horizontal = dimensionResource(id = R.dimen.card_side_margin),
vertical = dimensionResource(id = R.dimen.header_margin)
)
) {
items(
items = plants,
key = { it.plantId }
) { plant ->
PlantListItem(plant = plant) {
onPlantClick(plant)
}
}
}
}
@Composable
fun PlantListItem(plant: Plant, onClick: () -> Unit) {
ImageListItem(name = plant.name, imageUrl = plant.imageUrl, onClick = onClick)
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun SunflowerImage(
model : Any?,
contentDescription : String,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha : Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
requestBuilderTransform: RequestBuilderTransform<Drawable> ={it}
){
if(LocalInspectionMode.current){
Box(modifier = modifier.background(Color.Magenta))
return
}
GlideImage(model = model, contentDescription = contentDescription, modifier = modifier, alignment = alignment, contentScale = contentScale, alpha = alpha, colorFilter = colorFilter, requestBuilderTransform = requestBuilderTransform,
loading = placeholder{
Box(modifier.fillMaxWidth(), contentAlignment = Alignment.Center){
CircularProgressIndicator(Modifier.size(40.dp))
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ImageListItem(name: String, imageUrl:String, onClick: () -> Unit){
Card(
onClick = onClick,
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.card_side_margin))
.padding(bottom = dimensionResource(id = R.dimen.card_bottom_margin))
){
Column(Modifier.fillMaxHeight()) {
SunflowerImage(model = imageUrl, contentDescription = stringResource(id = R.string.a11y_plant_item_image),
Modifier
.fillMaxWidth()
.height(dimensionResource(id = R.dimen.plant_item_image_height)),
contentScale = ContentScale.Crop)
Text(text = name, textAlign = TextAlign.Center, maxLines = 1, style = MaterialTheme.typography.titleMedium, modifier = Modifier
.fillMaxWidth()
.padding(
vertical = dimensionResource(id = R.dimen.margin_normal))
.wrapContentWidth(Alignment.CenterHorizontally))
}
}
}
@Composable
fun GardenScreen(
modifier: Modifier = Modifier,
viewModel: GardenPlantingListViewModel = hiltViewModel(),
onAddPlantClick: () -> Unit,
onPlantClick: (PlantAndGardenPlantings) -> Unit
) {
val gardenPlants by viewModel.plantAndGardenPlantings.collectAsState(initial = emptyList())
GardenScreen(
gardenPlants = gardenPlants,
modifier = modifier,
onAddPlantClick = onAddPlantClick,
onPlantClick = onPlantClick
)
}
@Embedded
Room에서 obejct를 표현하기 위한 어노테이션입니다.
@Relation
부모와 자식 관계를 정의하기 위한 어노테이션입니다.
data class PlantAndGardenPlantings(
@Embedded
val plant:Plant,
@Relation(parentColumn = "id", entityColumn = "plant_id")
val gardenPlantings: List<GardenPlanting> = emptyList()
)
@Dao
interface GardenPlantingDao {
@Transaction
@Query("SELECT * FROM plants WHERE id IN (SELECT DISTINCT(plant_id) FROM garden_plantings)")
fun getPlantedGardens() : Flow<List<PlantAndGardenPlantings>>
}
@Singleton
class GardenPlantingRepository @Inject constructor(
private val gardenPlantingDao : GardenPlantingDao
){
fun getPlantedGardens() = gardenPlantingDao.getPlantedGardens()
}
@HiltViewModel
class GardenPlantingListViewModel @Inject internal constructor(
gardenPlantingRepository: GardenPlantingRepository
) : ViewModel() {
val plantAndGardenPlantings: Flow<List<PlantAndGardenPlantings>> =
gardenPlantingRepository.getPlantedGardens()
}
@Composable
fun GardenScreen(
modifier: Modifier = Modifier,
viewModel: GardenPlantingListViewModel = hiltViewModel(),
onAddPlantClick: () -> Unit,
onPlantClick: (PlantAndGardenPlantings) -> Unit
) {
val gardenPlants by viewModel.plantAndGardenPlantings.collectAsState(initial = emptyList())
GardenScreen(
gardenPlants = gardenPlants,
modifier = modifier,
onAddPlantClick = onAddPlantClick,
onPlantClick = onPlantClick
)
}
@Composable
fun GardenScreen(
gardenPlants: List<PlantAndGardenPlantings>,
modifier: Modifier = Modifier,
onAddPlantClick: () -> Unit = {},
onPlantClick: (PlantAndGardenPlantings) -> Unit = {}
) {
if (gardenPlants.isEmpty()) {
EmptyGarden(onAddPlantClick, modifier)
} else {
GardenList(gardenPlants = gardenPlants, onPlantClick = onPlantClick, modifier = modifier)
}
}
@Composable
private fun GardenList(
gardenPlants: List<PlantAndGardenPlantings>,
onPlantClick: (PlantAndGardenPlantings) -> Unit,
modifier: Modifier = Modifier,
) {
// Call reportFullyDrawn when the garden list has been rendered
val gridState = rememberLazyGridState()
ReportDrawnWhen { gridState.layoutInfo.totalItemsCount > 0 }
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier,
state = gridState,
contentPadding = PaddingValues(
horizontal = dimensionResource(id = R.dimen.card_side_margin),
vertical = dimensionResource(id = R.dimen.margin_normal)
)
) {
items(
items = gardenPlants,
key = { it.plant.plantId }
) {
GardenListItem(plant = it, onPlantClick = onPlantClick)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun GardenListItem(
plant: PlantAndGardenPlantings,
onPlantClick: (PlantAndGardenPlantings) -> Unit
) {
val vm = PlantAndGardenPlantingsViewModel(plant)
// Dimensions
val cardSideMargin = dimensionResource(id = R.dimen.card_side_margin)
val marginNormal = dimensionResource(id = R.dimen.margin_normal)
ElevatedCard(
onClick = { onPlantClick(plant) },
modifier = Modifier.padding(
start = cardSideMargin,
end = cardSideMargin,
bottom = dimensionResource(id = R.dimen.card_bottom_margin)
),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
) {
Column(Modifier.fillMaxWidth()) {
SunflowerImage(
model = vm.imageUrl,
contentDescription = plant.plant.description,
Modifier
.fillMaxWidth()
.height(dimensionResource(id = R.dimen.plant_item_image_height)),
contentScale = ContentScale.Crop,
)
// Plant name
Text(
text = vm.plantName,
Modifier
.padding(vertical = marginNormal)
.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleMedium,
)
// Planted date
Text(
text = stringResource(id = R.string.plant_date_header),
Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleSmall
)
Text(
text = vm.plantDateString,
Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.labelSmall
)
// Last Watered
Text(
text = stringResource(id = R.string.watered_date_header),
Modifier
.align(Alignment.CenterHorizontally)
.padding(top = marginNormal),
style = MaterialTheme.typography.titleSmall
)
Text(
text = vm.waterDateString,
Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.labelSmall
)
Text(
text = pluralStringResource(
id = R.plurals.watering_next,
count = vm.wateringInterval,
vm.wateringInterval
),
Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = marginNormal),
style = MaterialTheme.typography.labelSmall
)
}
}
}
@Composable
private fun EmptyGarden(onAddPlantClick: () -> Unit, modifier: Modifier = Modifier) {
// Calls reportFullyDrawn when this composable is composed.
ReportDrawn()
Column(
modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = R.string.garden_empty),
style = MaterialTheme.typography.headlineSmall
)
Button(
shape = MaterialTheme.shapes.medium,
onClick = onAddPlantClick
) {
Text(
text = stringResource(id = R.string.add_plant),
style = MaterialTheme.typography.titleSmall
)
}
}
}
@Provides
fun provideGardenPlantingDao(appDatabase: AppDatabase): GardenPlantingDao {
return appDatabase.gardenPlantingDao()
}
GardenActivity에서 이어지는 ui, viewmodel 등을 모두 작성한듯 하다.
지금까지는 단순 Build만 했고, 다음부터는 Run시키며 문제점 수정 및 추가 진행 해보도록 하자