diff --git a/shared/src/commonMain/kotlin/plus/rua/project/App.kt b/shared/src/commonMain/kotlin/plus/rua/project/App.kt index 0252a93..266cd7b 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/App.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/App.kt @@ -1,49 +1,15 @@ package plus.rua.project -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import org.jetbrains.compose.resources.painterResource - -import yaya.shared.generated.resources.Res -import yaya.shared.generated.resources.compose_multiplatform +import plus.rua.project.ui.CalendarMonthView @Composable @Preview fun App() { MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.primaryContainer) - .safeContentPadding() - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Button(onClick = { showContent = !showContent }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } - } - } + CalendarMonthView(modifier = Modifier) } -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/plus/rua/project/Greeting.kt b/shared/src/commonMain/kotlin/plus/rua/project/Greeting.kt deleted file mode 100644 index 57fd547..0000000 --- a/shared/src/commonMain/kotlin/plus/rua/project/Greeting.kt +++ /dev/null @@ -1,9 +0,0 @@ -package plus.rua.project - -class Greeting { - private val platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt new file mode 100644 index 0000000..9ec042a --- /dev/null +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -0,0 +1,43 @@ +package plus.rua.project.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import kotlin.time.Clock +import plus.rua.project.CalendarViewModel + +@Composable +fun CalendarMonthView( + viewModel: CalendarViewModel = remember { CalendarViewModel() }, + modifier: Modifier = Modifier +) { + val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) } + var currentYear by remember { mutableIntStateOf(viewModel.currentYear) } + var currentMonth by remember { mutableIntStateOf(viewModel.currentMonth) } + + Column(modifier = modifier.fillMaxSize()) { + MonthHeader( + year = currentYear, + month = currentMonth, + weekNumber = viewModel.getIsoWeekNumber(viewModel.selectedDate) + ) + CalendarPager( + selectedDate = viewModel.selectedDate, + today = today, + onDateClick = { date -> viewModel.selectDate(date) }, + onMonthChanged = { year, month -> + currentYear = year + currentMonth = month + }, + modifier = Modifier.weight(1f) + ) + } +} diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt new file mode 100644 index 0000000..affd37b --- /dev/null +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -0,0 +1,82 @@ +package plus.rua.project.ui + +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import kotlinx.coroutines.launch +import kotlinx.datetime.LocalDate + +private const val START_PAGE = Int.MAX_VALUE / 2 + +@Composable +fun CalendarPager( + selectedDate: LocalDate, + today: LocalDate, + onDateClick: (LocalDate) -> Unit, + onMonthChanged: (year: Int, month: Int) -> Unit, + modifier: Modifier = Modifier +) { + val initialYearMonth = remember { today.toYearMonth() } + val pagerState = rememberPagerState( + initialPage = START_PAGE, + pageCount = { Int.MAX_VALUE } + ) + val coroutineScope = rememberCoroutineScope() + + // Sync settled page to onMonthChanged + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.settledPage }.collect { page -> + val yearMonth = pageToYearMonth(page, initialYearMonth) + onMonthChanged(yearMonth.first, yearMonth.second) + } + } + + HorizontalPager( + state = pagerState, + beyondViewportPageCount = 1, + flingBehavior = PagerDefaults.flingBehavior(state = pagerState), + modifier = modifier + ) { page -> + val (year, month) = pageToYearMonth(page, initialYearMonth) + CalendarMonthPage( + year = year, + month = month, + selectedDate = selectedDate, + today = today, + onDateClick = { date -> + onDateClick(date) + // If clicking a date in a different month, scroll to that page + val clickedYearMonth = date.toYearMonth() + if (clickedYearMonth != pageToYearMonth(page, initialYearMonth)) { + val targetPage = yearMonthToPage(clickedYearMonth, initialYearMonth) + if (targetPage != pagerState.currentPage) { + coroutineScope.launch { + pagerState.animateScrollToPage(targetPage) + } + } + } + } + ) + } +} + +@Suppress("DEPRECATION") +private fun LocalDate.toYearMonth(): Pair = Pair(year, monthNumber) + +private fun pageToYearMonth(page: Int, initial: Pair): Pair { + val offset = page - START_PAGE + val totalMonths = initial.first * 12 + (initial.second - 1) + offset + return Pair(totalMonths / 12, totalMonths % 12 + 1) +} + +private fun yearMonthToPage(yearMonth: Pair, initial: Pair): Int { + val targetTotal = yearMonth.first * 12 + (yearMonth.second - 1) + val initialTotal = initial.first * 12 + (initial.second - 1) + return START_PAGE + (targetTotal - initialTotal) +}