package com.vgmlr.wedge
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.res.colorResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.ui.input.pointer.pointerInput
private const val MONTH_SPAN = 5
private const val GRID_ROWS = 6
private val TITLE_WIDTH = 132.dp
private val SWIPE_THRESHOLD = 48.dp
private data class DayCell(
val timestamp: Long,
val dayOfMonth: Int,
val otcDay: Long,
val inMonth: Boolean,
val isPast: Boolean,
val isToday: Boolean
)
private fun buildMonth(today: Long, monthOffset: Int): Pair<String, List<DayCell>> {
val monthStart = Calendar.getInstance().apply {
timeInMillis = today
set(Calendar.DAY_OF_MONTH, 1)
add(Calendar.MONTH, monthOffset)
}
val title = SimpleDateFormat("MMMM", Locale.US)
.format(monthStart.time)
.replaceFirstChar { it.uppercase(Locale.US) }
val lead = monthStart.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY
val month = monthStart.get(Calendar.MONTH)
val cursor = (monthStart.clone() as Calendar).apply { add(Calendar.DAY_OF_MONTH, -lead) }
val cells = ArrayList<DayCell>(GRID_ROWS * 7)
repeat(GRID_ROWS * 7) {
val ts = cursor.timeInMillis
cells.add(
DayCell(
timestamp = ts,
dayOfMonth = cursor.get(Calendar.DAY_OF_MONTH),
otcDay = WedgeDate.getOtcParts(ts).first,
inMonth = cursor.get(Calendar.MONTH) == month,
isPast = ts < today,
isToday = ts == today
)
)
cursor.add(Calendar.DAY_OF_MONTH, 1)
}
return title to cells
}
@Composable
fun WedgeCalendarPicker(
onDateSelected: (Long) -> Unit,
onDismiss: () -> Unit
) {
val fg = colorResource(id = R.color.dialog_color)
val today = remember { WedgeDate.startOfDay(System.currentTimeMillis()) }
var monthOffset by remember { mutableIntStateOf(0) }
val (title, cells) = remember(today, monthOffset) { buildMonth(today, monthOffset) }
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Surface(
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
color = colorResource(id = R.color.dialog_bg_color)
) {
Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 18.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
NavArrow(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
monthOffset > 0, fg
) { monthOffset-- }
Text(
text = title,
color = fg,
fontSize = 16.sp,
fontFamily = FontFamily.Monospace,
textAlign = TextAlign.Center,
modifier = Modifier.width(TITLE_WIDTH)
)
NavArrow(
Icons.AutoMirrored.Filled.KeyboardArrowRight,
monthOffset < MONTH_SPAN - 1, fg
) { monthOffset++ }
}
Spacer(Modifier.height(14.dp))
Row(modifier = Modifier.fillMaxWidth()) {
listOf("S", "M", "T", "W", "T", "F", "S").forEach { d ->
Text(
text = d,
color = fg.copy(alpha = 0.8f),
fontSize = 13.sp,
fontFamily = FontFamily.Monospace,
textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)
)
}
}
Spacer(Modifier.height(6.dp))
Column(
modifier = Modifier.pointerInput(Unit) {
val threshold = SWIPE_THRESHOLD.toPx()
var travel = 0f
detectHorizontalDragGestures(
onDragStart = { travel = 0f },
onDragCancel = { travel = 0f },
onDragEnd = {
when {
travel <= -threshold && monthOffset < MONTH_SPAN - 1 -> monthOffset++
travel >= threshold && monthOffset > 0 -> monthOffset--
}
}
) { change, amount ->
travel += amount
change.consume()
}
}
) {
for (row in 0 until GRID_ROWS) {
Row(modifier = Modifier.fillMaxWidth()) {
for (col in 0 until 7) {
DayCellView(
cell = cells[row * 7 + col],
fg = fg,
modifier = Modifier.weight(1f),
onPick = onDateSelected
)
}
}
}
}
}
}
}
}
@Composable
private fun NavArrow(icon: ImageVector, enabled: Boolean, fg: Color, onClick: () -> Unit) {
IconButton(onClick = onClick, enabled = enabled, modifier = Modifier.size(34.dp)) {
Icon(icon, null, tint = fg.copy(alpha = if (enabled) 1f else 0.2f))
}
}
@Composable
private fun DayCellView(
cell: DayCell,
fg: Color,
modifier: Modifier,
onPick: (Long) -> Unit
) {
val tappable = !cell.isPast
val alpha = when {
!cell.inMonth -> 0.22f
cell.isPast -> 0.3f
else -> 1f
}
Box(
modifier = modifier
.height(44.dp)
.then(
if (cell.isToday)
Modifier.border(1.dp, fg.copy(alpha = 0.6f), RoundedCornerShape(6.dp))
else Modifier
)
.clickable(enabled = tappable) { onPick(cell.timestamp) },
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = cell.dayOfMonth.toString(),
color = fg.copy(alpha = alpha),
fontSize = 15.sp,
lineHeight = 16.sp,
fontFamily = FontFamily.Monospace
)
Text(
text = cell.otcDay.toString(),
color = fg.copy(alpha = alpha * 0.7f),
fontSize = 12.sp,
lineHeight = 13.sp,
fontFamily = FontFamily.Monospace
)
}
}
}