Skip to content

Instantly share code, notes, and snippets.

@oianmol
Last active January 28, 2024 05:48
Show Gist options
  • Save oianmol/839b597df2ff689fdb34a80f25ef1eb8 to your computer and use it in GitHub Desktop.
Save oianmol/839b597df2ff689fdb34a80f25ef1eb8 to your computer and use it in GitHub Desktop.
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
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.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.TextStyle
import java.util.Locale
val keys = listOf("sun", "mon", "tue", "wed", "thu", "fri", "sat")
fun DayOfWeek.toKey(): String {
return when (this) {
DayOfWeek.MONDAY -> "mon"
DayOfWeek.TUESDAY -> "tue"
DayOfWeek.WEDNESDAY -> "wed"
DayOfWeek.THURSDAY -> "thu"
DayOfWeek.FRIDAY -> "fri"
DayOfWeek.SATURDAY -> "sat"
DayOfWeek.SUNDAY -> "sun"
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GithubChart(
chartDetail: ChartDetail,
) {
val months = remember(chartDetail) {
JetYear.current(firstDayOfWeek = DayOfWeek.MONDAY).yearMonths
}
LazyRow(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.Bottom,
) {
stickyHeader {
Column {
DayOfWeek.values().forEach { dayOfWeek ->
Text(
dayOfWeek.getDisplayName(TextStyle.NARROW, Locale.getDefault()),
style = com.atomicdev.atomicui.theme.Typography.bodySmall.copy(
fontFamily = FontFamily.Default,
fontWeight = FontWeight(510),
color = Color(0xff242424),
),
modifier = Modifier.padding(4.dp),
)
}
}
}
itemsIndexed(months) { index, jetMonth ->
val datesWithinHabit =
jetMonth.monthWeeks.map { jetWeek ->
jetWeek.days
}.flatten()
val groupedDayOfWeekAndDates = datesWithinHabit
.groupBy { it.date.dayOfWeek }
.toSortedMap { o1, o2 ->
keys.indexOf(o1.toKey()).compareTo(keys.indexOf(o2.toKey()))
}
val monthHeaderName = when {
index == 1 && months.getOrNull(index.minus(1))
?.year() != jetMonth.year() -> jetMonth.monthYear()
else -> jetMonth.name()
}
Column(Modifier.padding(start = 12.dp)) {
Text(monthHeaderName)
groupedDayOfWeekAndDates.forEach {
val days = it.value
Row {
days.forEach { jetDay ->
Box {
if (jetDay.isPartOfMonth) {
Box(
modifier = Modifier
.padding(4.dp)
.size(16.dp)
.background(
if (chartDetail.commitDates.contains(jetDay.date)) Color.Yellow else Color.Gray,
shape = RoundedCornerShape(4.dp),
),
)
} else {
Box(
modifier = Modifier
.padding(4.dp)
.size(16.dp)
.background(
Color.Gray,
shape = RoundedCornerShape(4.dp),
),
)
}
}
}
}
}
}
}
}
}
@Preview
@Composable
private fun PreviewGithubChart() {
MaterialTheme {
Box(Modifier.fillMaxSize()) {
GithubChart(
ChartDetail(
commitDates = JetYear.current(firstDayOfWeek = DayOfWeek.MONDAY).yearMonths.map {
it.monthWeeks.map {
it.dates().map { it.date }.random()
}
}.flatten(),
),
)
}
}
}
data class ChartDetail(val commitDates: List<LocalDate>)
import java.time.LocalDate
open class JetCalendarType
data class JetDay(val date: LocalDate, val isPartOfMonth: Boolean) : JetCalendarType()
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
import java.time.format.TextStyle
import java.time.temporal.TemporalAdjusters
import java.time.temporal.WeekFields
import java.util.*
class JetMonth private constructor(
val startDate: LocalDate,
val endDate: LocalDate,
var firstDayOfWeek: DayOfWeek,
) : JetCalendarType() {
lateinit var monthWeeks: List<JetWeek>
fun name(): String {
return startDate.month.getDisplayName(
TextStyle.SHORT,
Locale.getDefault()
)
}
fun monthYear(): String {
return name() + " " + year()
}
fun year(): String {
return startDate.year.toString()
}
companion object {
fun current(
date: LocalDate = LocalDate.now(),
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
): JetMonth {
val startOfMonth = date.with(TemporalAdjusters.firstDayOfMonth())
val endOfMonth = date.with(TemporalAdjusters.lastDayOfMonth())
val month = JetMonth(startOfMonth, endOfMonth, firstDayOfWeek = firstDayOfWeek)
month.monthWeeks = month.weeks(firstDayOfWeek)
return month
}
}
private fun weeks(firstDayOfWeek: DayOfWeek): List<JetWeek> {
val currentYearMonth: YearMonth = YearMonth.of(this.endDate.year, this.endDate.monthValue)
val weeks = currentYearMonth.atEndOfMonth().get(WeekFields.of(firstDayOfWeek, 1).weekOfMonth())
val monthWeeks = mutableListOf<JetWeek>()
monthWeeks.add(
JetWeek.current(
startDate,
dayOfWeek = this.firstDayOfWeek
)
)
while (monthWeeks.size != weeks) {
monthWeeks.add(monthWeeks.last().nextWeek())
}
return monthWeeks
}
}
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.TextStyle
import java.time.temporal.TemporalAdjusters
import java.util.*
fun dayNames(dayOfWeek: DayOfWeek): List<String> {
val days = mutableListOf<DayOfWeek>()
days.add(dayOfWeek)
while (days.size != 7) {
days.add(days.last().plus(1))
}
return days.map {
it.getDisplayName(
TextStyle.NARROW,
Locale.getDefault()
)
}
}
class JetWeek private constructor(
val startDate: LocalDate,
val endDate: LocalDate,
val monthOfWeek: Int,
val firstDayOfWeek: DayOfWeek,
) : JetCalendarType() {
lateinit var days: List<JetDay>
companion object {
fun current(
date: LocalDate = LocalDate.now(),
dayOfWeek: DayOfWeek
): JetWeek {
val startOfCurrentWeek: LocalDate =
date.with(TemporalAdjusters.previousOrSame(dayOfWeek))
val lastDayOfWeek = dayOfWeek.plus(6) // or minus(1)
val endOfWeek: LocalDate = date.with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
val week = JetWeek(startOfCurrentWeek, endOfWeek, date.monthValue, dayOfWeek)
week.days = week.dates()
return week
}
}
fun dates(): List<JetDay> {
val days = mutableListOf<JetDay>()
val isPart = startDate.monthValue == this.monthOfWeek
days.add(startDate.toJetDay(isPart))
while (days.size != 7) {
days.add(days.last().nextDay(this))
}
return days
}
}
fun LocalDate.toJetDay(isPart: Boolean): JetDay {
return JetDay(this, isPart)
}
private fun JetDay.nextDay(jetWeek: JetWeek): JetDay {
val date = this.date.plusDays(1)
val isPartOfMonth = this.date.plusDays(1).monthValue == jetWeek.monthOfWeek
return JetDay(date, isPartOfMonth)
}
fun JetWeek.nextWeek(): JetWeek {
val firstDay = this.endDate.plusDays(1)
val week = JetWeek.current(firstDay, dayOfWeek = firstDayOfWeek)
week.days = week.dates()
return week
}
enum class JetViewType {
MONTHLY,
WEEKLY,
YEARLY;
fun next(): JetViewType {
if (ordinal == values().size.minus(1)) {
return MONTHLY
}
return values()[ordinal + 1]
}
}
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.temporal.TemporalAdjusters
import java.time.temporal.WeekFields
import java.util.Locale
class JetYear private constructor(
val startDate: LocalDate,
) : JetCalendarType() {
lateinit var yearMonths: List<JetMonth>
companion object {
fun current(
date: LocalDate = LocalDate.now(),
firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
): JetYear {
val day: LocalDate = date.with(TemporalAdjusters.firstDayOfYear())
val last: LocalDate = date.with(TemporalAdjusters.lastDayOfYear())
val year = JetYear(day)
year.yearMonths = year.months(firstDayOfWeek)
return year
}
fun monthsBetween(
startDate: LocalDate,
endDate: LocalDate,
): List<JetMonth> {
val jetMonths = mutableListOf<JetMonth>()
var firstDate = startDate
while (true) {
jetMonths.add(JetMonth.current(firstDate))
firstDate = firstDate.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1)
if (firstDate > endDate) {
break
}
}
return jetMonths
}
}
private fun months(firstDayOfWeek: DayOfWeek): List<JetMonth> {
val months = mutableListOf<JetMonth>()
var startDateMonth = this.startDate.withDayOfMonth(1)
var endDateMonth = this.startDate.withDayOfMonth(this.startDate.lengthOfMonth())
var currentYear = this.startDate.year
while (true) {
months.add(JetMonth.current(startDateMonth, firstDayOfWeek))
startDateMonth = endDateMonth.plusDays(1)
endDateMonth = startDateMonth.withDayOfMonth(startDateMonth.lengthOfMonth())
if (endDateMonth.year > currentYear) {
break
}
currentYear = endDateMonth.year
}
return months
}
fun year(): String {
return this.startDate.year.toString()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment