Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-99&100] 약속 잡기 STEP 3 - 캘린더 구현 & 캘린더를 공통 컴포넌트로 분리 #101

Merged
merged 7 commits into from
Jul 22, 2022
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.yapp.growth.presentation.component


import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.prolificinteractive.materialcalendarview.CalendarDay
import com.prolificinteractive.materialcalendarview.MaterialCalendarView
import com.prolificinteractive.materialcalendarview.OnDateSelectedListener
import com.yapp.growth.presentation.ui.main.home.CalendarDecorator

@Composable
fun PlanzCalendar(
currentDate: CalendarDay,
selectMode: PlanzCalendarSelectMode,
selectedDates: List<CalendarDay> = emptyList(),
onDateSelectedListener: OnDateSelectedListener,
) {
val context = LocalContext.current

AndroidView(
modifier = Modifier
.padding(bottom = 12.dp),
factory = {
MaterialCalendarView(it).apply {
this.isDynamicHeightEnabled = true
this.topbarVisible = false
this.isPagingEnabled = false
this.setOnDateChangedListener(onDateSelectedListener)

when(selectMode) {
PlanzCalendarSelectMode.SINGLE -> {
this.selectionMode = MaterialCalendarView.SELECTION_MODE_SINGLE
this.showOtherDates = MaterialCalendarView.SHOW_OTHER_MONTHS
this.setAllowClickDaysOutsideCurrentMonth(false)
}
PlanzCalendarSelectMode.MULTIPLE -> {
this.selectionMode = MaterialCalendarView.SELECTION_MODE_MULTIPLE
this.showOtherDates = MaterialCalendarView.SHOW_DEFAULTS
}
}
}
},
update = { views ->
views.apply {
this.currentDate = currentDate

if(selectedDates.isNotEmpty()) {
for(selectDate in selectedDates) {
this.setDateSelected(selectDate, true)
}
}

this.addDecorator(CalendarDecorator.SelectDecorator(context, this))
this.addDecorator(CalendarDecorator.SundayDecorator())

when(selectMode) {
PlanzCalendarSelectMode.SINGLE -> {
this.addDecorator(CalendarDecorator.OtherMonthDisableSelectionDecorator(context, this))
this.addDecorator(CalendarDecorator.TodayDecorator(context))
this.addDecorator(CalendarDecorator.DotDecorator())
}
PlanzCalendarSelectMode.MULTIPLE -> {
this.addDecorator(CalendarDecorator.SelectedDatesDecorator(context, this.selectedDates))
this.addDecorator(CalendarDecorator.TodayBoldDecorator())
this.addDecorator(CalendarDecorator.TodayLessDecorator(context))
}
}
}
}
)
}

enum class PlanzCalendarSelectMode {
SINGLE,
MULTIPLE
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CreatePlanContract {
val theme: PlanThemeType? = null,
val title: String = "",
val place: String = "",
val dates: String = "",
val dates: List<String> = emptyList(),
val startHour: Int = -1,
val endHour: Int = -1,
) : ViewState
Expand All @@ -23,7 +23,7 @@ class CreatePlanContract {
data class DecideTheme(val theme: PlanThemeType) : CreatePlanEvent()
data class DecideTitle(val title: String) : CreatePlanEvent()
data class DecidePlace(val place: String) : CreatePlanEvent()
data class DecideDates(val dates: String) : CreatePlanEvent() // TODO: 전달받는 타입에 맞춰 변경
data class DecideDates(val dates: List<String>) : CreatePlanEvent()
data class DecideTimeRange(val startHour: Int, val endHour: Int) : CreatePlanEvent()
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package com.yapp.growth.presentation.ui.createPlan.date

import com.prolificinteractive.materialcalendarview.CalendarDay
import com.yapp.growth.base.ViewEvent
import com.yapp.growth.base.ViewSideEffect
import com.yapp.growth.base.ViewState

class DateContract {
data class DateViewState(
val dates: String = "",
val dates: List<CalendarDay> = emptyList(),
val isDatesEmpty: Boolean = false,
) : ViewState

sealed class DateSideEffect : ViewSideEffect {
object ExitCreateScreen : DateSideEffect()
object NavigateToNextScreen : DateSideEffect()
object NavigateToPreviousScreen : DateSideEffect()
data class ShowSnackBar(val msg: String) : DateSideEffect()
}

sealed class DateEvent : ViewEvent {
object OnClickExitButton : DateEvent()
object OnClickNextButton : DateEvent()
object OnClickBackButton : DateEvent()
data class OnDateSelected(val dates: List<CalendarDay>) : DateEvent()
object OnPreviousDateClicked : DateEvent()
object OnDateOverSelected : DateEvent()
object OnMonthlyPreviousClicked : DateEvent()
object OnMonthlyNextClicked : DateEvent()
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
package com.yapp.growth.presentation.ui.createPlan.date

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import android.content.Context
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Icon
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarHost
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.prolificinteractive.materialcalendarview.CalendarDay
import com.yapp.growth.presentation.R
import com.yapp.growth.presentation.component.PlanzButtonWithBack
import com.yapp.growth.presentation.component.PlanzCalendar
import com.yapp.growth.presentation.component.PlanzCalendarSelectMode
import com.yapp.growth.presentation.component.PlanzCreateStepTitleWithDescription
import com.yapp.growth.presentation.component.PlanzErrorSnackBar
import com.yapp.growth.presentation.theme.PlanzTypography
import com.yapp.growth.presentation.ui.createPlan.CreatePlanContract.CreatePlanEvent.DecideDates
import com.yapp.growth.presentation.ui.createPlan.CreatePlanViewModel
import com.yapp.growth.presentation.ui.createPlan.date.DateContract.DateEvent
import com.yapp.growth.presentation.ui.createPlan.date.DateContract.DateSideEffect
import com.yapp.growth.presentation.ui.main.home.CalendarDecorator
import com.yapp.growth.presentation.util.composableActivityViewModel
import com.yapp.growth.presentation.util.toFormatDate
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

@Composable
fun DateScreen(
Expand All @@ -31,36 +58,51 @@ fun DateScreen(
navigateToNextScreen: () -> Unit,
navigateToPreviousScreen: () -> Unit,
) {
val context = LocalContext.current
val viewState by viewModel.viewState.collectAsState()
val currentDate by viewModel.currentDate.collectAsState()
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()

Scaffold(
scaffoldState = scaffoldState,
topBar = {
PlanzCreateStepTitleWithDescription(
currentStep = 3,
title = stringResource(id = R.string.create_plan_date_title_text),
description = stringResource(id = R.string.create_plan_date_max_day_text),
onExitClick = { viewModel.setEvent(DateEvent.OnClickExitButton) }
)
},
snackbarHost = { snackbarHostState ->
SnackbarHost(hostState = snackbarHostState) { snackbarData ->
PlanzErrorSnackBar(message = snackbarData.message)
}
}
) { padding ->
Box(
modifier = Modifier
.padding(padding)
.fillMaxWidth()
.fillMaxHeight()
.padding(padding)
) {
Column {
Spacer(modifier = Modifier.height(36.dp))
DateCalendar(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 40.dp)
currentDate = currentDate,
context = context,
onDateSelect = { viewModel.setEvent(DateEvent.OnDateSelected(it)) },
onMonthlyPreviousClick = { viewModel.setEvent(DateEvent.OnMonthlyPreviousClicked) },
onMonthlyNextClick = { viewModel.setEvent(DateEvent.OnMonthlyNextClicked) },
onPreviousClick = { viewModel.setEvent(DateEvent.OnPreviousDateClicked) },
onOverDateSelect = { viewModel.setEvent(DateEvent.OnDateOverSelected) },
selectedDates = viewState.dates
)
}

PlanzButtonWithBack(
text = stringResource(id = R.string.create_plan_next_button_text),
enabled = true,
enabled = viewState.dates.isNotEmpty(),
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(bottom = 32.dp),
Expand All @@ -71,17 +113,21 @@ fun DateScreen(
}

LaunchedEffect(key1 = viewModel.effect) {
viewModel.effect.collect { effect ->
viewModel.effect.collectLatest { effect ->
when (effect) {
is DateSideEffect.ExitCreateScreen -> {
exitCreateScreen()
}
is DateSideEffect.ExitCreateScreen -> { exitCreateScreen() }
is DateSideEffect.NavigateToNextScreen -> {
sharedViewModel.setEvent(DecideDates(viewState.dates))
sharedViewModel.setEvent(
DecideDates(viewState.dates.map { it.toFormatDate() })
)
navigateToNextScreen()
}
is DateSideEffect.NavigateToPreviousScreen -> {
navigateToPreviousScreen()
is DateSideEffect.NavigateToPreviousScreen -> { navigateToPreviousScreen() }
is DateSideEffect.ShowSnackBar -> {
coroutineScope.launch {
scaffoldState.snackbarHostState.currentSnackbarData?.dismiss()
scaffoldState.snackbarHostState.showSnackbar(effect.msg)
}
}
}
}
Expand All @@ -90,16 +136,67 @@ fun DateScreen(

@Composable
fun DateCalendar(
modifier: Modifier = Modifier,
currentDate: CalendarDay,
context: Context,
selectedDates: List<CalendarDay>,
onDateSelect: (List<CalendarDay>) -> Unit,
onMonthlyPreviousClick: () -> Unit,
onMonthlyNextClick: () -> Unit,
onPreviousClick: () -> Unit,
onOverDateSelect: () -> Unit,
) {
// TODO: 캘린더 추가
val year: Int = currentDate.year
val month: Int = currentDate.month + 1

Box(
modifier = modifier
.background(Color.Black)
.height(280.dp)
Column(
modifier = Modifier.padding(horizontal = 32.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.clickable { onMonthlyPreviousClick() },
tint = Color.Unspecified,
imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_box_left_24),
contentDescription = null,
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = "${year}년 ${String.format("%02d", month)}월",
style = PlanzTypography.h3,
)
Spacer(modifier = Modifier.width(16.dp))
Icon(
modifier = Modifier.clickable { onMonthlyNextClick() },
tint = Color.Unspecified,
imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_box_right_24),
contentDescription = null,
)
}
PlanzCalendar(
currentDate = currentDate,
selectedDates = selectedDates,
selectMode = PlanzCalendarSelectMode.MULTIPLE,
onDateSelectedListener = { widget, date, _ ->
if (date.isBefore(CalendarDay.today())) {
widget.setDateSelected(date, false)
widget.addDecorator(CalendarDecorator.TodayLessDecorator(context))
onPreviousClick()
} else {
if (widget.selectedDates.size > 12) {
widget.setDateSelected(date, false)
onOverDateSelect()
}
widget.addDecorator(
CalendarDecorator.SelectedDatesDecorator(context, widget.selectedDates)
)
}

onDateSelect(widget.selectedDates)
},
)
}
}

Expand Down
Loading