Last active
January 28, 2024 05:48
-
-
Save oianmol/839b597df2ff689fdb34a80f25ef1eb8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.LocalDate | |
open class JetCalendarType | |
data class JetDay(val date: LocalDate, val isPartOfMonth: Boolean) : JetCalendarType() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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