Implement swipeable month view with HorizontalPager and assemble app
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
edf881d1cc
commit
eb8b16047a
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
package plus.rua.project
|
||||
|
||||
class Greeting {
|
||||
private val platform = getPlatform()
|
||||
|
||||
fun greet(): String {
|
||||
return "Hello, ${platform.name}!"
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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<Int, Int> = Pair(year, monthNumber)
|
||||
|
||||
private fun pageToYearMonth(page: Int, initial: Pair<Int, Int>): Pair<Int, Int> {
|
||||
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<Int, Int>, initial: Pair<Int, Int>): Int {
|
||||
val targetTotal = yearMonth.first * 12 + (yearMonth.second - 1)
|
||||
val initialTotal = initial.first * 12 + (initial.second - 1)
|
||||
return START_PAGE + (targetTotal - initialTotal)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user