mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Remove DateCalculator
This commit is contained in:
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,9 +1,16 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
*.pbxuser
|
||||||
local.properties
|
*.perspective
|
||||||
.idea
|
*.perspectivev3
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build
|
|
||||||
captures
|
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
.gradle
|
||||||
|
.idea
|
||||||
|
build
|
||||||
|
build/
|
||||||
|
captures
|
||||||
|
local.properties
|
||||||
node_modules
|
node_modules
|
||||||
|
*xcuserdata*
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ buildscript {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools.build:gradle:3.2.1"
|
classpath "com.android.tools.build:gradle:3.2.1"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,17 +42,11 @@ apply plugin:"kotlin-multiplatform"
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
targets {
|
targets {
|
||||||
|
def iosPreset = System.getenv('SDK_NAME')?.startsWith("iphoneos") ? presets.iosArm64 : presets.iosX64
|
||||||
fromPreset(presets.jvm, 'jvm')
|
fromPreset(presets.jvm, 'jvm')
|
||||||
|
fromPreset(iosPreset, 'iOS') {
|
||||||
fromPreset(presets.iosX64, 'iOS') {
|
|
||||||
compilations.main.outputKinds('FRAMEWORK')
|
compilations.main.outputKinds('FRAMEWORK')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the target above by the following one to produce a framework
|
|
||||||
// which can be installed on a real iPhone:
|
|
||||||
// fromPreset(presets.iosArm64, 'iOS') {
|
|
||||||
// compilations.main.outputKinds('FRAMEWORK')
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -31,48 +31,143 @@ enum class DayOfWeek(val index: Int) {
|
|||||||
SATURDAY(6),
|
SATURDAY(6),
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Timestamp(val unixTimeInMillis: Long)
|
data class Timestamp(val millisSince1970: Long) {
|
||||||
|
val localDate: LocalDate
|
||||||
|
get() {
|
||||||
|
val millisSince2000 = millisSince1970 - 946684800000
|
||||||
|
val daysSince2000 = millisSince2000 / 86400000
|
||||||
|
return LocalDate(daysSince2000.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class LocalDate(val year: Int,
|
data class LocalDate(val daysSince2000: Int) {
|
||||||
val month: Int,
|
|
||||||
val day: Int) {
|
var yearCache = -1
|
||||||
|
var monthCache = -1
|
||||||
|
var dayCache = -1
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (daysSince2000 < 0)
|
||||||
|
throw IllegalArgumentException("$daysSince2000 < 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(year: Int, month: Int, day: Int) :
|
||||||
|
this(daysSince2000(year, month, day))
|
||||||
|
|
||||||
|
val dayOfWeek: DayOfWeek
|
||||||
|
get() {
|
||||||
|
return when (daysSince2000 % 7) {
|
||||||
|
0 -> DayOfWeek.SATURDAY
|
||||||
|
1 -> DayOfWeek.SUNDAY
|
||||||
|
2 -> DayOfWeek.MONDAY
|
||||||
|
3 -> DayOfWeek.TUESDAY
|
||||||
|
4 -> DayOfWeek.WEDNESDAY
|
||||||
|
5 -> DayOfWeek.THURSDAY
|
||||||
|
else -> DayOfWeek.FRIDAY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val timestamp: Timestamp
|
||||||
|
get() {
|
||||||
|
return Timestamp(946684800000 + daysSince2000.toLong() * 86400000)
|
||||||
|
}
|
||||||
|
|
||||||
|
val year: Int
|
||||||
|
get() {
|
||||||
|
if (yearCache < 0) updateYearMonthDayCache()
|
||||||
|
return yearCache
|
||||||
|
}
|
||||||
|
|
||||||
|
val month: Int
|
||||||
|
get() {
|
||||||
|
if (monthCache < 0) updateYearMonthDayCache()
|
||||||
|
return monthCache
|
||||||
|
}
|
||||||
|
|
||||||
|
val day: Int
|
||||||
|
get() {
|
||||||
|
if (dayCache < 0) updateYearMonthDayCache()
|
||||||
|
return dayCache
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateYearMonthDayCache() {
|
||||||
|
var currYear = 2000
|
||||||
|
var currDay = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val currYearLength = if (isLeapYear(currYear)) 366 else 365
|
||||||
|
if (daysSince2000 < currDay + currYearLength) {
|
||||||
|
yearCache = currYear
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
currYear++
|
||||||
|
currDay += currYearLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currMonth = 1
|
||||||
|
val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (daysSince2000 < currDay + monthOffset[currMonth]) {
|
||||||
|
monthCache = currMonth
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
currMonth++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currDay += monthOffset[currMonth - 1]
|
||||||
|
dayCache = daysSince2000 - currDay + 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun isOlderThan(other: LocalDate): Boolean {
|
fun isOlderThan(other: LocalDate): Boolean {
|
||||||
if (other.year != year) return other.year > year
|
return daysSince2000 < other.daysSince2000
|
||||||
if (other.month != month) return other.month > month
|
|
||||||
return other.day > day
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNewerThan(other: LocalDate): Boolean {
|
fun isNewerThan(other: LocalDate): Boolean {
|
||||||
if (this == other) return false
|
return daysSince2000 > other.daysSince2000
|
||||||
return other.isOlderThan(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
fun plus(days: Int): LocalDate {
|
||||||
if ((month <= 0) or (month >= 13)) throw(IllegalArgumentException())
|
return LocalDate(daysSince2000 + days)
|
||||||
if ((day <= 0) or (day >= 32)) throw(IllegalArgumentException())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LocalDateCalculator {
|
fun minus(days: Int): LocalDate {
|
||||||
fun plusDays(date: LocalDate, days: Int): LocalDate
|
return LocalDate(daysSince2000 - days)
|
||||||
fun dayOfWeek(date: LocalDate): DayOfWeek
|
|
||||||
fun toTimestamp(date: LocalDate): Timestamp
|
|
||||||
fun fromTimestamp(timestamp: Timestamp): LocalDate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LocalDateCalculator.distanceInDays(d1: LocalDate, d2: LocalDate): Int {
|
fun distanceTo(other: LocalDate): Int {
|
||||||
val t1 = toTimestamp(d1)
|
return abs(daysSince2000 - other.daysSince2000)
|
||||||
val t2 = toTimestamp(d2)
|
|
||||||
val dayLength = 24 * 60 * 60 * 1000
|
|
||||||
return abs((t2.unixTimeInMillis - t1.unixTimeInMillis) / dayLength).toInt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LocalDateCalculator.minusDays(date: LocalDate, days: Int): LocalDate {
|
|
||||||
return plusDays(date, -days)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LocalDateFormatter {
|
interface LocalDateFormatter {
|
||||||
fun shortWeekdayName(date: LocalDate): String
|
fun shortWeekdayName(date: LocalDate): String
|
||||||
fun shortMonthName(date: LocalDate): String
|
fun shortMonthName(date: LocalDate): String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isLeapYear(year: Int): Boolean {
|
||||||
|
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val leapOffset = arrayOf(0, 31, 60, 91, 121, 152, 182,
|
||||||
|
213, 244, 274, 305, 335, 366)
|
||||||
|
val nonLeapOffset = arrayOf(0, 31, 59, 90, 120, 151, 181,
|
||||||
|
212, 243, 273, 304, 334, 365)
|
||||||
|
|
||||||
|
private fun daysSince2000(year: Int, month: Int, day: Int): Int {
|
||||||
|
|
||||||
|
var result = 365 * (year - 2000)
|
||||||
|
result += ceil((year - 2000) / 4.0).toInt()
|
||||||
|
result -= ceil((year - 2000) / 100.0).toInt()
|
||||||
|
result += ceil((year - 2000) / 400.0).toInt()
|
||||||
|
if (isLeapYear(year)) {
|
||||||
|
result += leapOffset[month - 1]
|
||||||
|
} else {
|
||||||
|
result += nonLeapOffset[month - 1]
|
||||||
|
}
|
||||||
|
result += (day - 1)
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ class Backend(databaseName: String,
|
|||||||
databaseOpener: DatabaseOpener,
|
databaseOpener: DatabaseOpener,
|
||||||
fileOpener: FileOpener,
|
fileOpener: FileOpener,
|
||||||
val log: Log,
|
val log: Log,
|
||||||
val dateCalculator: LocalDateCalculator,
|
|
||||||
val taskRunner: TaskRunner) {
|
val taskRunner: TaskRunner) {
|
||||||
|
|
||||||
val database: Database
|
val database: Database
|
||||||
@@ -56,13 +55,12 @@ class Backend(databaseName: String,
|
|||||||
database = databaseOpener.open(dbFile)
|
database = databaseOpener.open(dbFile)
|
||||||
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||||
habitsRepository = HabitRepository(database)
|
habitsRepository = HabitRepository(database)
|
||||||
checkmarkRepository = CheckmarkRepository(database, dateCalculator)
|
checkmarkRepository = CheckmarkRepository(database)
|
||||||
taskRunner.runInBackground {
|
taskRunner.runInBackground {
|
||||||
habits.putAll(habitsRepository.findAll())
|
habits.putAll(habitsRepository.findAll())
|
||||||
for ((key, habit) in habits) {
|
for ((key, habit) in habits) {
|
||||||
val checks = checkmarkRepository.findAll(key)
|
val checks = checkmarkRepository.findAll(key)
|
||||||
checkmarks[habit] = CheckmarkList(habit.frequency,
|
checkmarks[habit] = CheckmarkList(habit.frequency)
|
||||||
dateCalculator)
|
|
||||||
checkmarks[habit]?.setManualCheckmarks(checks)
|
checkmarks[habit]?.setManualCheckmarks(checks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +74,7 @@ class Backend(databaseName: String,
|
|||||||
habit.id = id
|
habit.id = id
|
||||||
habit.position = habits.size
|
habit.position = habits.size
|
||||||
habits[id] = habit
|
habits[id] = habit
|
||||||
checkmarks[habit] = CheckmarkList(habit.frequency, dateCalculator)
|
checkmarks[habit] = CheckmarkList(habit.frequency)
|
||||||
habitsRepository.insert(habit)
|
habitsRepository.insert(habit)
|
||||||
mainScreenDataSource.requestData()
|
mainScreenDataSource.requestData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import kotlin.math.*
|
|||||||
class CalendarChart(var today: LocalDate,
|
class CalendarChart(var today: LocalDate,
|
||||||
var color: Color,
|
var color: Color,
|
||||||
var theme: Theme,
|
var theme: Theme,
|
||||||
var dateCalculator: LocalDateCalculator,
|
|
||||||
var dateFormatter: LocalDateFormatter) : Component {
|
var dateFormatter: LocalDateFormatter) : Component {
|
||||||
|
|
||||||
var padding = 5.0
|
var padding = 5.0
|
||||||
@@ -47,19 +46,19 @@ class CalendarChart(var today: LocalDate,
|
|||||||
canvas.setFontSize(height * 0.06)
|
canvas.setFontSize(height * 0.06)
|
||||||
|
|
||||||
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
|
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
|
||||||
val todayWeekday = dateCalculator.dayOfWeek(today)
|
val todayWeekday = today.dayOfWeek
|
||||||
val topLeftOffset = (nColumns - 1 + scrollPosition) * 7 + todayWeekday.index
|
val topLeftOffset = (nColumns - 1 + scrollPosition) * 7 + todayWeekday.index
|
||||||
val topLeftDate = dateCalculator.minusDays(today, topLeftOffset)
|
val topLeftDate = today.minus(topLeftOffset)
|
||||||
|
|
||||||
repeat(nColumns) { column ->
|
repeat(nColumns) { column ->
|
||||||
val topOffset = topLeftOffset - 7 * column
|
val topOffset = topLeftOffset - 7 * column
|
||||||
val topDate = dateCalculator.plusDays(topLeftDate, 7 * column)
|
val topDate = topLeftDate.plus(7 * column)
|
||||||
drawColumn(canvas, column, topDate, topOffset)
|
drawColumn(canvas, column, topDate, topOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
canvas.setColor(theme.mediumContrastTextColor)
|
||||||
repeat(7) { row ->
|
repeat(7) { row ->
|
||||||
val date = dateCalculator.plusDays(topLeftDate, row)
|
val date = topLeftDate.plus(row)
|
||||||
canvas.setTextAlign(TextAlign.LEFT)
|
canvas.setTextAlign(TextAlign.LEFT)
|
||||||
canvas.drawText(dateFormatter.shortWeekdayName(date),
|
canvas.drawText(dateFormatter.shortWeekdayName(date),
|
||||||
padding + nColumns * squareSize + padding,
|
padding + nColumns * squareSize + padding,
|
||||||
@@ -74,7 +73,7 @@ class CalendarChart(var today: LocalDate,
|
|||||||
drawHeader(canvas, column, topDate)
|
drawHeader(canvas, column, topDate)
|
||||||
repeat(7) { row ->
|
repeat(7) { row ->
|
||||||
val offset = topOffset - row
|
val offset = topOffset - row
|
||||||
val date = dateCalculator.plusDays(topDate, row)
|
val date = topDate.plus(row)
|
||||||
if (offset < 0) return
|
if (offset < 0) return
|
||||||
drawSquare(canvas,
|
drawSquare(canvas,
|
||||||
padding + column * squareSize,
|
padding + column * squareSize,
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ import org.isoron.platform.time.*
|
|||||||
class HabitListHeader(private val today: LocalDate,
|
class HabitListHeader(private val today: LocalDate,
|
||||||
private val nButtons: Int,
|
private val nButtons: Int,
|
||||||
private val theme: Theme,
|
private val theme: Theme,
|
||||||
private val fmt: LocalDateFormatter,
|
private val fmt: LocalDateFormatter) : Component {
|
||||||
private val calc: LocalDateCalculator) : Component {
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
val width = canvas.getWidth()
|
||||||
@@ -44,7 +43,7 @@ class HabitListHeader(private val today: LocalDate,
|
|||||||
canvas.setFontSize(theme.smallTextSize)
|
canvas.setFontSize(theme.smallTextSize)
|
||||||
|
|
||||||
repeat(nButtons) { index ->
|
repeat(nButtons) { index ->
|
||||||
val date = calc.minusDays(today, nButtons - index - 1)
|
val date = today.minus(nButtons - index - 1)
|
||||||
val name = fmt.shortWeekdayName(date).toUpperCase()
|
val name = fmt.shortWeekdayName(date).toUpperCase()
|
||||||
val number = date.day.toString()
|
val number = date.day.toString()
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_AUTOMATIC
|
|||||||
import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_MANUAL
|
import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_MANUAL
|
||||||
import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
|
import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
|
||||||
|
|
||||||
class CheckmarkList(private val frequency: Frequency,
|
class CheckmarkList(private val frequency: Frequency) {
|
||||||
private val dateCalculator: LocalDateCalculator) {
|
|
||||||
|
|
||||||
private val manualCheckmarks = mutableListOf<Checkmark>()
|
private val manualCheckmarks = mutableListOf<Checkmark>()
|
||||||
private val automaticCheckmarks = mutableListOf<Checkmark>()
|
private val automaticCheckmarks = mutableListOf<Checkmark>()
|
||||||
@@ -39,8 +38,7 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
automaticCheckmarks.clear()
|
automaticCheckmarks.clear()
|
||||||
manualCheckmarks.addAll(checks)
|
manualCheckmarks.addAll(checks)
|
||||||
automaticCheckmarks.addAll(computeAutomaticCheckmarks(checks,
|
automaticCheckmarks.addAll(computeAutomaticCheckmarks(checks,
|
||||||
frequency,
|
frequency))
|
||||||
dateCalculator))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +54,7 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
|
|
||||||
val result = mutableListOf<Int>()
|
val result = mutableListOf<Int>()
|
||||||
val newest = automaticCheckmarks.first().date
|
val newest = automaticCheckmarks.first().date
|
||||||
val distToNewest = dateCalculator.distanceInDays(newest, date)
|
val distToNewest = newest.distanceTo(date)
|
||||||
|
|
||||||
var fromIndex = 0
|
var fromIndex = 0
|
||||||
val toIndex = automaticCheckmarks.size
|
val toIndex = automaticCheckmarks.size
|
||||||
@@ -75,13 +73,12 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
* Computes the list of automatic checkmarks a list of manual ones.
|
* Computes the list of automatic checkmarks a list of manual ones.
|
||||||
*/
|
*/
|
||||||
fun computeAutomaticCheckmarks(checks: List<Checkmark>,
|
fun computeAutomaticCheckmarks(checks: List<Checkmark>,
|
||||||
frequency: Frequency,
|
frequency: Frequency
|
||||||
calc: LocalDateCalculator
|
|
||||||
): MutableList<Checkmark> {
|
): MutableList<Checkmark> {
|
||||||
|
|
||||||
val intervals = buildIntervals(checks, frequency, calc)
|
val intervals = buildIntervals(checks, frequency)
|
||||||
snapIntervalsTogether(intervals, calc)
|
snapIntervalsTogether(intervals)
|
||||||
return buildCheckmarksFromIntervals(checks, intervals, calc)
|
return buildCheckmarksFromIntervals(checks, intervals)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,19 +89,17 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
* the interval, however, still falls within the interval. The length of
|
* the interval, however, still falls within the interval. The length of
|
||||||
* the intervals are also not modified.
|
* the intervals are also not modified.
|
||||||
*/
|
*/
|
||||||
fun snapIntervalsTogether(intervals: MutableList<Interval>,
|
fun snapIntervalsTogether(intervals: MutableList<Interval>) {
|
||||||
calc: LocalDateCalculator) {
|
|
||||||
|
|
||||||
for (i in 1 until intervals.size) {
|
for (i in 1 until intervals.size) {
|
||||||
val (begin, center, end) = intervals[i]
|
val (begin, center, end) = intervals[i]
|
||||||
val (_, _, prevEnd) = intervals[i - 1]
|
val (_, _, prevEnd) = intervals[i - 1]
|
||||||
|
|
||||||
val gap = calc.distanceInDays(prevEnd, begin) - 1
|
val gap = prevEnd.distanceTo(begin) - 1
|
||||||
val endMinusGap = calc.minusDays(end, gap)
|
if (gap <= 0 || end.minus(gap).isOlderThan(center)) continue
|
||||||
if (gap <= 0 || endMinusGap.isOlderThan(center)) continue
|
intervals[i] = Interval(begin.minus(gap),
|
||||||
intervals[i] = Interval(calc.minusDays(begin, gap),
|
|
||||||
center,
|
center,
|
||||||
calc.minusDays(end, gap))
|
end.minus(gap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +114,7 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
* receive unchecked checkmarks.
|
* receive unchecked checkmarks.
|
||||||
*/
|
*/
|
||||||
fun buildCheckmarksFromIntervals(checks: List<Checkmark>,
|
fun buildCheckmarksFromIntervals(checks: List<Checkmark>,
|
||||||
intervals: List<Interval>,
|
intervals: List<Interval>
|
||||||
calc: LocalDateCalculator
|
|
||||||
): MutableList<Checkmark> {
|
): MutableList<Checkmark> {
|
||||||
|
|
||||||
if (checks.isEmpty()) throw IllegalArgumentException()
|
if (checks.isEmpty()) throw IllegalArgumentException()
|
||||||
@@ -137,25 +131,25 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
if (check.date.isNewerThan(newest)) newest = check.date
|
if (check.date.isNewerThan(newest)) newest = check.date
|
||||||
}
|
}
|
||||||
|
|
||||||
val distance = calc.distanceInDays(oldest, newest)
|
val distance = oldest.distanceTo(newest)
|
||||||
val checkmarks = mutableListOf<Checkmark>()
|
val checkmarks = mutableListOf<Checkmark>()
|
||||||
for (offset in 0..distance)
|
for (offset in 0..distance)
|
||||||
checkmarks.add(Checkmark(calc.minusDays(newest, offset),
|
checkmarks.add(Checkmark(newest.minus(offset),
|
||||||
UNCHECKED))
|
UNCHECKED))
|
||||||
|
|
||||||
for (interval in intervals) {
|
for (interval in intervals) {
|
||||||
val beginOffset = calc.distanceInDays(newest, interval.begin)
|
val beginOffset = newest.distanceTo(interval.begin)
|
||||||
val endOffset = calc.distanceInDays(newest, interval.end)
|
val endOffset = newest.distanceTo(interval.end)
|
||||||
|
|
||||||
for (offset in endOffset..beginOffset) {
|
for (offset in endOffset..beginOffset) {
|
||||||
checkmarks.set(offset,
|
checkmarks.set(offset,
|
||||||
Checkmark(calc.minusDays(newest, offset),
|
Checkmark(newest.minus(offset),
|
||||||
CHECKED_AUTOMATIC))
|
CHECKED_AUTOMATIC))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (check in checks) {
|
for (check in checks) {
|
||||||
val offset = calc.distanceInDays(newest, check.date)
|
val offset = newest.distanceTo(check.date)
|
||||||
checkmarks.set(offset, Checkmark(check.date, CHECKED_MANUAL))
|
checkmarks.set(offset, Checkmark(check.date, CHECKED_MANUAL))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +161,7 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
* checkmarks.
|
* checkmarks.
|
||||||
*/
|
*/
|
||||||
fun buildIntervals(checks: List<Checkmark>,
|
fun buildIntervals(checks: List<Checkmark>,
|
||||||
frequency: Frequency,
|
frequency: Frequency): MutableList<Interval> {
|
||||||
calc: LocalDateCalculator): MutableList<Interval> {
|
|
||||||
|
|
||||||
val num = frequency.numerator
|
val num = frequency.numerator
|
||||||
val den = frequency.denominator
|
val den = frequency.denominator
|
||||||
@@ -178,10 +171,9 @@ class CheckmarkList(private val frequency: Frequency,
|
|||||||
val first = checks[i]
|
val first = checks[i]
|
||||||
val last = checks[i + num - 1]
|
val last = checks[i + num - 1]
|
||||||
|
|
||||||
val distance = calc.distanceInDays(first.date, last.date)
|
if (first.date.distanceTo(last.date) >= den) continue
|
||||||
if (distance >= den) continue
|
|
||||||
|
|
||||||
val end = calc.plusDays(first.date, den - 1)
|
val end = first.date.plus(den - 1)
|
||||||
intervals.add(Interval(first.date, last.date, end))
|
intervals.add(Interval(first.date, last.date, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ package org.isoron.uhabits.models
|
|||||||
import org.isoron.platform.io.*
|
import org.isoron.platform.io.*
|
||||||
import org.isoron.platform.time.*
|
import org.isoron.platform.time.*
|
||||||
|
|
||||||
class CheckmarkRepository(db: Database,
|
class CheckmarkRepository(db: Database) {
|
||||||
val dateCalculator: LocalDateCalculator) {
|
|
||||||
|
|
||||||
private val findStatement = db.prepareStatement("select timestamp, value from Repetitions where habit = ? order by timestamp desc")
|
private val findStatement = db.prepareStatement("select timestamp, value from Repetitions where habit = ? order by timestamp desc")
|
||||||
private val insertStatement = db.prepareStatement("insert into Repetitions(habit, timestamp, value) values (?, ?, ?)")
|
private val insertStatement = db.prepareStatement("insert into Repetitions(habit, timestamp, value) values (?, ?, ?)")
|
||||||
@@ -33,9 +32,8 @@ class CheckmarkRepository(db: Database,
|
|||||||
findStatement.bindInt(0, habitId)
|
findStatement.bindInt(0, habitId)
|
||||||
val result = mutableListOf<Checkmark>()
|
val result = mutableListOf<Checkmark>()
|
||||||
while (findStatement.step() == StepResult.ROW) {
|
while (findStatement.step() == StepResult.ROW) {
|
||||||
val timestamp = Timestamp(findStatement.getLong(0))
|
val date = Timestamp(findStatement.getLong(0)).localDate
|
||||||
val value = findStatement.getInt(1)
|
val value = findStatement.getInt(1)
|
||||||
val date = dateCalculator.fromTimestamp(timestamp)
|
|
||||||
result.add(Checkmark(date, value))
|
result.add(Checkmark(date, value))
|
||||||
}
|
}
|
||||||
findStatement.reset()
|
findStatement.reset()
|
||||||
@@ -43,18 +41,18 @@ class CheckmarkRepository(db: Database,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun insert(habitId: Int, checkmark: Checkmark) {
|
fun insert(habitId: Int, checkmark: Checkmark) {
|
||||||
val timestamp = dateCalculator.toTimestamp(checkmark.date)
|
val timestamp = checkmark.date.timestamp
|
||||||
insertStatement.bindInt(0, habitId)
|
insertStatement.bindInt(0, habitId)
|
||||||
insertStatement.bindLong(1, timestamp.unixTimeInMillis)
|
insertStatement.bindLong(1, timestamp.millisSince1970)
|
||||||
insertStatement.bindInt(2, checkmark.value)
|
insertStatement.bindInt(2, checkmark.value)
|
||||||
insertStatement.step()
|
insertStatement.step()
|
||||||
insertStatement.reset()
|
insertStatement.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(habitId: Int, date: LocalDate) {
|
fun delete(habitId: Int, date: LocalDate) {
|
||||||
val timestamp = dateCalculator.toTimestamp(date)
|
val timestamp = date.timestamp
|
||||||
deleteStatement.bindInt(0, habitId)
|
deleteStatement.bindInt(0, habitId)
|
||||||
deleteStatement.bindLong(1, timestamp.unixTimeInMillis)
|
deleteStatement.bindLong(1, timestamp.millisSince1970)
|
||||||
deleteStatement.step()
|
deleteStatement.step()
|
||||||
deleteStatement.reset()
|
deleteStatement.reset()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,20 @@
|
|||||||
|
|
||||||
package org.isoron.platform.time
|
package org.isoron.platform.time
|
||||||
|
|
||||||
import java.lang.Math.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.Calendar.*
|
import java.util.Calendar.*
|
||||||
|
|
||||||
|
|
||||||
fun LocalDate.toGregorianCalendar(): GregorianCalendar {
|
fun LocalDate.toGregorianCalendar(): GregorianCalendar {
|
||||||
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
|
val cal = GregorianCalendar()
|
||||||
cal.set(Calendar.HOUR_OF_DAY, 0)
|
cal.timeZone = TimeZone.getTimeZone("GMT")
|
||||||
cal.set(Calendar.MINUTE, 0)
|
cal.set(MILLISECOND, 0)
|
||||||
cal.set(Calendar.SECOND, 0)
|
cal.set(SECOND, 0)
|
||||||
cal.set(Calendar.MILLISECOND, 0)
|
cal.set(MINUTE, 0)
|
||||||
cal.set(Calendar.YEAR, this.year)
|
cal.set(HOUR_OF_DAY, 0)
|
||||||
cal.set(Calendar.MONTH, this.month - 1)
|
cal.set(YEAR, this.year)
|
||||||
cal.set(Calendar.DAY_OF_MONTH, this.day)
|
cal.set(MONTH, this.month - 1)
|
||||||
|
cal.set(DAY_OF_MONTH, this.day)
|
||||||
return cal
|
return cal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,35 +57,3 @@ class JavaLocalDateFormatter(private val locale: Locale) : LocalDateFormatter {
|
|||||||
return cal.getDisplayName(DAY_OF_WEEK, SHORT, locale);
|
return cal.getDisplayName(DAY_OF_WEEK, SHORT, locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JavaLocalDateCalculator : LocalDateCalculator {
|
|
||||||
override fun toTimestamp(date: LocalDate): Timestamp {
|
|
||||||
val cal = date.toGregorianCalendar()
|
|
||||||
return Timestamp(cal.timeInMillis)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fromTimestamp(timestamp: Timestamp): LocalDate {
|
|
||||||
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
|
|
||||||
cal.timeInMillis = timestamp.unixTimeInMillis
|
|
||||||
return cal.toLocalDate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dayOfWeek(date: LocalDate): DayOfWeek {
|
|
||||||
val cal = date.toGregorianCalendar()
|
|
||||||
return when (cal.get(DAY_OF_WEEK)) {
|
|
||||||
Calendar.SATURDAY -> DayOfWeek.SATURDAY
|
|
||||||
Calendar.SUNDAY -> DayOfWeek.SUNDAY
|
|
||||||
Calendar.MONDAY -> DayOfWeek.MONDAY
|
|
||||||
Calendar.TUESDAY -> DayOfWeek.TUESDAY
|
|
||||||
Calendar.WEDNESDAY -> DayOfWeek.WEDNESDAY
|
|
||||||
Calendar.THURSDAY -> DayOfWeek.THURSDAY
|
|
||||||
else -> DayOfWeek.FRIDAY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun plusDays(date: LocalDate, days: Int): LocalDate {
|
|
||||||
val cal = date.toGregorianCalendar()
|
|
||||||
cal.add(Calendar.DAY_OF_MONTH, days)
|
|
||||||
return cal.toLocalDate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,10 +23,10 @@ import junit.framework.TestCase.*
|
|||||||
import org.isoron.platform.time.*
|
import org.isoron.platform.time.*
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.Calendar.*
|
||||||
|
|
||||||
|
|
||||||
class JavaDatesTest {
|
class JavaDatesTest {
|
||||||
private val calc = JavaLocalDateCalculator()
|
|
||||||
private val d1 = LocalDate(2019, 3, 25)
|
private val d1 = LocalDate(2019, 3, 25)
|
||||||
private val d2 = LocalDate(2019, 4, 4)
|
private val d2 = LocalDate(2019, 4, 4)
|
||||||
private val d3 = LocalDate(2019, 5, 12)
|
private val d3 = LocalDate(2019, 5, 12)
|
||||||
@@ -34,46 +34,46 @@ class JavaDatesTest {
|
|||||||
@Test
|
@Test
|
||||||
fun plusMinusDays() {
|
fun plusMinusDays() {
|
||||||
val today = LocalDate(2019, 3, 25)
|
val today = LocalDate(2019, 3, 25)
|
||||||
assertEquals(calc.minusDays(today, 28), LocalDate(2019, 2, 25))
|
assertEquals(today.minus(28), LocalDate(2019, 2, 25))
|
||||||
assertEquals(calc.plusDays(today, 7), LocalDate(2019, 4, 1))
|
assertEquals(today.plus(7), LocalDate(2019, 4, 1))
|
||||||
assertEquals(calc.plusDays(today, 42), LocalDate(2019, 5, 6))
|
assertEquals(today.plus(42), LocalDate(2019, 5, 6))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shortMonthName() {
|
fun shortMonthName() {
|
||||||
var fmt = JavaLocalDateFormatter(Locale.US)
|
var fmt = JavaLocalDateFormatter(Locale.US)
|
||||||
assertEquals(fmt.shortWeekdayName(d1), "Mon")
|
assertEquals("Mon", fmt.shortWeekdayName(d1))
|
||||||
assertEquals(fmt.shortWeekdayName(d2), "Thu")
|
assertEquals("Thu", fmt.shortWeekdayName(d2))
|
||||||
assertEquals(fmt.shortWeekdayName(d3), "Sun")
|
assertEquals("Sun", fmt.shortWeekdayName(d3))
|
||||||
assertEquals(fmt.shortMonthName(d1), "Mar")
|
assertEquals("Mar", fmt.shortMonthName(d1))
|
||||||
assertEquals(fmt.shortMonthName(d2), "Apr")
|
assertEquals("Apr", fmt.shortMonthName(d2))
|
||||||
assertEquals(fmt.shortMonthName(d3), "May")
|
assertEquals("May", fmt.shortMonthName(d3))
|
||||||
|
|
||||||
fmt = JavaLocalDateFormatter(Locale.JAPAN)
|
fmt = JavaLocalDateFormatter(Locale.JAPAN)
|
||||||
assertEquals(fmt.shortWeekdayName(d1), "月")
|
assertEquals("月", fmt.shortWeekdayName(d1))
|
||||||
assertEquals(fmt.shortWeekdayName(d2), "木")
|
assertEquals("木", fmt.shortWeekdayName(d2))
|
||||||
assertEquals(fmt.shortWeekdayName(d3), "日")
|
assertEquals("日", fmt.shortWeekdayName(d3))
|
||||||
assertEquals(fmt.shortMonthName(d1), "3月")
|
assertEquals("3月", fmt.shortMonthName(d1))
|
||||||
assertEquals(fmt.shortMonthName(d2), "4月")
|
assertEquals("4月", fmt.shortMonthName(d2))
|
||||||
assertEquals(fmt.shortMonthName(d3), "5月")
|
assertEquals("5月", fmt.shortMonthName(d3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun weekDay() {
|
fun weekDay() {
|
||||||
assertEquals(DayOfWeek.SUNDAY, calc.dayOfWeek(LocalDate(2015, 1, 25)))
|
assertEquals(DayOfWeek.SUNDAY, LocalDate(2015, 1, 25).dayOfWeek)
|
||||||
assertEquals(DayOfWeek.MONDAY, calc.dayOfWeek(LocalDate(2017, 7, 3)))
|
assertEquals(DayOfWeek.MONDAY, LocalDate(2017, 7, 3).dayOfWeek)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun timestamps() {
|
fun timestamps() {
|
||||||
val timestamps = listOf(Timestamp(1555977600000),
|
val timestamps = listOf(Timestamp(1555977600000),
|
||||||
Timestamp(968716800000),
|
Timestamp(968716800000),
|
||||||
Timestamp(0))
|
Timestamp(946684800000))
|
||||||
val dates = listOf(LocalDate(2019, 4, 23),
|
val dates = listOf(LocalDate(2019, 4, 23),
|
||||||
LocalDate(2000, 9, 12),
|
LocalDate(2000, 9, 12),
|
||||||
LocalDate(1970, 1, 1))
|
LocalDate(2000, 1, 1))
|
||||||
assertEquals(timestamps, dates.map { d -> calc.toTimestamp(d) })
|
assertEquals(timestamps, dates.map { d -> d.timestamp })
|
||||||
assertEquals(dates, timestamps.map { t -> calc.fromTimestamp(t) })
|
assertEquals(dates, timestamps.map { t -> t.localDate })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -96,10 +96,47 @@ class JavaDatesTest {
|
|||||||
val d2 = LocalDate(2019, 5, 30)
|
val d2 = LocalDate(2019, 5, 30)
|
||||||
val d3 = LocalDate(2019, 6, 5)
|
val d3 = LocalDate(2019, 6, 5)
|
||||||
|
|
||||||
assertEquals(0, calc.distanceInDays(d1, d1))
|
assertEquals(0, d1.distanceTo(d1))
|
||||||
assertEquals(20, calc.distanceInDays(d1, d2))
|
assertEquals(20, d1.distanceTo(d2))
|
||||||
assertEquals(20, calc.distanceInDays(d2, d1))
|
assertEquals(20, d2.distanceTo(d1))
|
||||||
assertEquals(26, calc.distanceInDays(d1, d3))
|
assertEquals(26, d1.distanceTo(d3))
|
||||||
assertEquals(6, calc.distanceInDays(d2, d3))
|
assertEquals(6, d2.distanceTo(d3))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun gregorianCalendarConversion() {
|
||||||
|
fun check(cal: GregorianCalendar, daysSince2000: Int) {
|
||||||
|
val year = cal.get(YEAR)
|
||||||
|
val month = cal.get(MONTH) + 1
|
||||||
|
val day = cal.get(DAY_OF_MONTH)
|
||||||
|
val weekday = cal.get(DAY_OF_WEEK)
|
||||||
|
val date = LocalDate(year, month, day)
|
||||||
|
val millisSince1970 = cal.timeInMillis
|
||||||
|
val msg = "date=$year-$month-$day offset=$daysSince2000"
|
||||||
|
|
||||||
|
assertEquals(msg, daysSince2000, date.daysSince2000)
|
||||||
|
assertEquals(msg, year, date.year)
|
||||||
|
assertEquals(msg, month, date.month)
|
||||||
|
assertEquals(msg, day, date.day)
|
||||||
|
assertEquals(msg, weekday, date.dayOfWeek.index + 1)
|
||||||
|
assertEquals(msg, millisSince1970, date.timestamp.millisSince1970)
|
||||||
|
assertEquals(msg, date, date.timestamp.localDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cal = GregorianCalendar()
|
||||||
|
cal.timeZone = TimeZone.getTimeZone("GMT")
|
||||||
|
cal.set(MILLISECOND, 0)
|
||||||
|
cal.set(SECOND, 0)
|
||||||
|
cal.set(MINUTE, 0)
|
||||||
|
cal.set(HOUR_OF_DAY, 0)
|
||||||
|
cal.set(DAY_OF_MONTH, 1)
|
||||||
|
cal.set(MONTH, 0)
|
||||||
|
cal.set(YEAR, 2000)
|
||||||
|
|
||||||
|
// Check all dates from year 2000 until 2400
|
||||||
|
for(offset in 0..146097) {
|
||||||
|
check(cal, offset)
|
||||||
|
cal.add(DAY_OF_YEAR, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ open class BaseTest {
|
|||||||
|
|
||||||
val databaseOpener = JavaDatabaseOpener(log)
|
val databaseOpener = JavaDatabaseOpener(log)
|
||||||
|
|
||||||
val dateCalculator = JavaLocalDateCalculator()
|
|
||||||
|
|
||||||
val taskRunner = SequentialTaskRunner()
|
val taskRunner = SequentialTaskRunner()
|
||||||
|
|
||||||
lateinit var db: Database
|
lateinit var db: Database
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class BackendTest : BaseTest() {
|
|||||||
databaseOpener,
|
databaseOpener,
|
||||||
fileOpener,
|
fileOpener,
|
||||||
log,
|
log,
|
||||||
dateCalculator,
|
|
||||||
taskRunner)
|
taskRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class CalendarChartTest : BaseViewTest() {
|
|||||||
val component = CalendarChart(LocalDate(2015, 1, 25),
|
val component = CalendarChart(LocalDate(2015, 1, 25),
|
||||||
theme.color(4),
|
theme.color(4),
|
||||||
theme,
|
theme,
|
||||||
JavaLocalDateCalculator(),
|
|
||||||
JavaLocalDateFormatter(Locale.US))
|
JavaLocalDateFormatter(Locale.US))
|
||||||
component.series = listOf(1.0, // today
|
component.series = listOf(1.0, // today
|
||||||
0.2, 0.5, 0.7, 0.0, 0.3, 0.4, 0.6,
|
0.2, 0.5, 0.7, 0.0, 0.3, 0.4, 0.6,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ class HabitListHeaderTest : BaseViewTest() {
|
|||||||
val header = HabitListHeader(LocalDate(2019, 3, 25),
|
val header = HabitListHeader(LocalDate(2019, 3, 25),
|
||||||
5,
|
5,
|
||||||
theme,
|
theme,
|
||||||
JavaLocalDateFormatter(Locale.US),
|
JavaLocalDateFormatter(Locale.US))
|
||||||
JavaLocalDateCalculator())
|
|
||||||
assertRenders(1200, 96,
|
assertRenders(1200, 96,
|
||||||
"components/HabitListHeader/light.png",
|
"components/HabitListHeader/light.png",
|
||||||
header)
|
header)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
private val today = LocalDate(2019, 1, 30)
|
private val today = LocalDate(2019, 1, 30)
|
||||||
|
|
||||||
private fun day(offset: Int): LocalDate {
|
private fun day(offset: Int): LocalDate {
|
||||||
return dateCalculator.minusDays(today, offset)
|
return today.minus(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -45,8 +45,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
CheckmarkList.Interval(day(18), day(18), day(12)),
|
CheckmarkList.Interval(day(18), day(18), day(12)),
|
||||||
CheckmarkList.Interval(day(8), day(8), day(2)))
|
CheckmarkList.Interval(day(8), day(8), day(2)))
|
||||||
val actual = CheckmarkList.buildIntervals(checks,
|
val actual = CheckmarkList.buildIntervals(checks,
|
||||||
Frequency.WEEKLY,
|
Frequency.WEEKLY)
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +59,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
CheckmarkList.Interval(day(18), day(18), day(18)),
|
CheckmarkList.Interval(day(18), day(18), day(18)),
|
||||||
CheckmarkList.Interval(day(8), day(8), day(8)))
|
CheckmarkList.Interval(day(8), day(8), day(8)))
|
||||||
val actual = CheckmarkList.buildIntervals(checks,
|
val actual = CheckmarkList.buildIntervals(checks,
|
||||||
Frequency.DAILY,
|
Frequency.DAILY)
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +75,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
CheckmarkList.Interval(day(22), day(18), day(16)),
|
CheckmarkList.Interval(day(22), day(18), day(16)),
|
||||||
CheckmarkList.Interval(day(18), day(15), day(12)))
|
CheckmarkList.Interval(day(18), day(15), day(12)))
|
||||||
val actual = CheckmarkList.buildIntervals(checks,
|
val actual = CheckmarkList.buildIntervals(checks,
|
||||||
Frequency.TWO_TIMES_PER_WEEK,
|
Frequency.TWO_TIMES_PER_WEEK)
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +91,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
CheckmarkList.Interval(day(25), day(25), day(19)),
|
CheckmarkList.Interval(day(25), day(25), day(19)),
|
||||||
CheckmarkList.Interval(day(18), day(16), day(12)),
|
CheckmarkList.Interval(day(18), day(16), day(12)),
|
||||||
CheckmarkList.Interval(day(11), day(8), day(5)))
|
CheckmarkList.Interval(day(11), day(8), day(5)))
|
||||||
CheckmarkList.snapIntervalsTogether(original, dateCalculator)
|
CheckmarkList.snapIntervalsTogether(original)
|
||||||
assertEquals(expected, original)
|
assertEquals(expected, original)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +115,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
Checkmark(day(9), CHECKED_AUTOMATIC),
|
Checkmark(day(9), CHECKED_AUTOMATIC),
|
||||||
Checkmark(day(10), CHECKED_MANUAL))
|
Checkmark(day(10), CHECKED_MANUAL))
|
||||||
val actual = CheckmarkList.buildCheckmarksFromIntervals(checks,
|
val actual = CheckmarkList.buildCheckmarksFromIntervals(checks,
|
||||||
intervals,
|
intervals)
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +130,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
Checkmark(day(4), CHECKED_AUTOMATIC),
|
Checkmark(day(4), CHECKED_AUTOMATIC),
|
||||||
Checkmark(day(5), CHECKED_AUTOMATIC))
|
Checkmark(day(5), CHECKED_AUTOMATIC))
|
||||||
val actual = CheckmarkList.buildCheckmarksFromIntervals(reps,
|
val actual = CheckmarkList.buildCheckmarksFromIntervals(reps,
|
||||||
intervals,
|
intervals)
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,14 +153,13 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
Checkmark(day(9), CHECKED_AUTOMATIC),
|
Checkmark(day(9), CHECKED_AUTOMATIC),
|
||||||
Checkmark(day(10), CHECKED_MANUAL))
|
Checkmark(day(10), CHECKED_MANUAL))
|
||||||
val actual = CheckmarkList.computeAutomaticCheckmarks(checks,
|
val actual = CheckmarkList.computeAutomaticCheckmarks(checks,
|
||||||
Frequency(1, 3),
|
Frequency(1, 3))
|
||||||
dateCalculator)
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetValuesUntil() {
|
fun testGetValuesUntil() {
|
||||||
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
|
val list = CheckmarkList(Frequency(1, 2))
|
||||||
list.setManualCheckmarks(listOf(Checkmark(day(4), CHECKED_MANUAL),
|
list.setManualCheckmarks(listOf(Checkmark(day(4), CHECKED_MANUAL),
|
||||||
Checkmark(day(7), CHECKED_MANUAL)))
|
Checkmark(day(7), CHECKED_MANUAL)))
|
||||||
val expected = listOf(UNCHECKED,
|
val expected = listOf(UNCHECKED,
|
||||||
@@ -188,7 +182,7 @@ class CheckmarkListTest : BaseTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetValuesUntil2() {
|
fun testGetValuesUntil2() {
|
||||||
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
|
val list = CheckmarkList(Frequency(1, 2))
|
||||||
val expected = listOf<Int>()
|
val expected = listOf<Int>()
|
||||||
assertEquals(expected, list.getValuesUntil(day(0)))
|
assertEquals(expected, list.getValuesUntil(day(0)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class CheckmarkRepositoryTest : BaseTest() {
|
|||||||
Checkmark(LocalDate(2019, 1, 25), 450),
|
Checkmark(LocalDate(2019, 1, 25), 450),
|
||||||
Checkmark(LocalDate(2019, 1, 20), 1000))
|
Checkmark(LocalDate(2019, 1, 20), 1000))
|
||||||
|
|
||||||
val repository = CheckmarkRepository(db, JavaLocalDateCalculator())
|
val repository = CheckmarkRepository(db)
|
||||||
|
|
||||||
for (c in checkmarksA) repository.insert(habitA, c)
|
for (c in checkmarksA) repository.insert(habitA, c)
|
||||||
for (c in checkmarksB) repository.insert(habitB, c)
|
for (c in checkmarksB) repository.insert(habitB, c)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import UIKit
|
|||||||
databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
|
databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
|
||||||
fileOpener: IosFileOpener(),
|
fileOpener: IosFileOpener(),
|
||||||
log: StandardLog(),
|
log: StandardLog(),
|
||||||
dateCalculator: IosLocalDateCalculator(),
|
|
||||||
taskRunner: SequentialTaskRunner())
|
taskRunner: SequentialTaskRunner())
|
||||||
|
|
||||||
func application(_ application: UIApplication,
|
func application(_ application: UIApplication,
|
||||||
@@ -35,7 +34,7 @@ import UIKit
|
|||||||
window = UIWindow(frame: UIScreen.main.bounds)
|
window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
if let window = window {
|
if let window = window {
|
||||||
let nav = UINavigationController()
|
let nav = UINavigationController()
|
||||||
nav.viewControllers = [ListHabitsController(withBackend: backend)]
|
nav.viewControllers = [MainScreenController(withBackend: backend)]
|
||||||
window.backgroundColor = UIColor.white
|
window.backgroundColor = UIColor.white
|
||||||
window.rootViewController = nav
|
window.rootViewController = nav
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ListHabitsCell : UITableViewCell {
|
class MainScreenCell : UITableViewCell {
|
||||||
var ring: ComponentView
|
var ring: ComponentView
|
||||||
var label = UILabel()
|
var label = UILabel()
|
||||||
var buttons: [ComponentView] = []
|
var buttons: [ComponentView] = []
|
||||||
@@ -48,7 +48,7 @@ class ListHabitsCell : UITableViewCell {
|
|||||||
label.heightAnchor.constraint(equalToConstant: size).isActive = true
|
label.heightAnchor.constraint(equalToConstant: size).isActive = true
|
||||||
stack.addArrangedSubview(label)
|
stack.addArrangedSubview(label)
|
||||||
|
|
||||||
for _ in 1...4 {
|
for _ in 1...3 {
|
||||||
let btn = ComponentView(frame: frame, component: nil)
|
let btn = ComponentView(frame: frame, component: nil)
|
||||||
btn.backgroundColor = .white
|
btn.backgroundColor = .white
|
||||||
btn.widthAnchor.constraint(equalToConstant: size).isActive = true
|
btn.widthAnchor.constraint(equalToConstant: size).isActive = true
|
||||||
@@ -88,7 +88,7 @@ class ListHabitsCell : UITableViewCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListHabitsController: UITableViewController, MainScreenDataSourceListener {
|
class MainScreenController: UITableViewController, MainScreenDataSourceListener {
|
||||||
var backend: Backend
|
var backend: Backend
|
||||||
var dataSource: MainScreenDataSource
|
var dataSource: MainScreenDataSource
|
||||||
var data: MainScreenDataSource.Data?
|
var data: MainScreenDataSource.Data?
|
||||||
@@ -119,7 +119,7 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
|
|||||||
target: self,
|
target: self,
|
||||||
action: #selector(self.onCreateHabitClicked))
|
action: #selector(self.onCreateHabitClicked))
|
||||||
]
|
]
|
||||||
tableView.register(ListHabitsCell.self, forCellReuseIdentifier: "cell")
|
tableView.register(MainScreenCell.self, forCellReuseIdentifier: "cell")
|
||||||
tableView.backgroundColor = theme.headerBackgroundColor.uicolor
|
tableView.backgroundColor = theme.headerBackgroundColor.uicolor
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
|
|||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let row = indexPath.row
|
let row = indexPath.row
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ListHabitsCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MainScreenCell
|
||||||
let color = theme.color(paletteIndex: data!.colors[row].index)
|
let color = theme.color(paletteIndex: data!.colors[row].index)
|
||||||
cell.label.text = data!.names[row]
|
cell.label.text = data!.names[row]
|
||||||
cell.setColor(color)
|
cell.setColor(color)
|
||||||
@@ -149,10 +149,9 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
|
|||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
let component = HabitListHeader(today: LocalDate(year: 2019, month: 3, day: 24),
|
let component = HabitListHeader(today: LocalDate(year: 2019, month: 3, day: 24),
|
||||||
nButtons: 4,
|
nButtons: 3,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
fmt: IosLocalDateFormatter(),
|
fmt: IosLocalDateFormatter())
|
||||||
calc: IosLocalDateCalculator())
|
|
||||||
return ComponentView(frame: CGRect(x: 0, y: 0, width: 100, height: CGFloat(theme.checkmarkButtonSize)),
|
return ComponentView(frame: CGRect(x: 0, y: 0, width: 100, height: CGFloat(theme.checkmarkButtonSize)),
|
||||||
component: component)
|
component: component)
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,6 @@ class ShowHabitController : UITableViewController {
|
|||||||
let component = CalendarChart(today: LocalDate(year: 2019, month: 3, day: 15),
|
let component = CalendarChart(today: LocalDate(year: 2019, month: 3, day: 15),
|
||||||
color: color,
|
color: color,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
dateCalculator: IosLocalDateCalculator(),
|
|
||||||
dateFormatter: IosLocalDateFormatter())
|
dateFormatter: IosLocalDateFormatter())
|
||||||
let cell = UITableViewCell()
|
let cell = UITableViewCell()
|
||||||
let view = ComponentView(frame: cell.frame, component: component)
|
let view = ComponentView(frame: cell.frame, component: component)
|
||||||
|
|||||||
@@ -54,35 +54,3 @@ class IosLocalDateFormatter : NSObject, LocalDateFormatter {
|
|||||||
return fmt.string(from: date.iosDate)
|
return fmt.string(from: date.iosDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IosLocalDateCalculator : NSObject, LocalDateCalculator {
|
|
||||||
func toTimestamp(date: LocalDate) -> Timestamp {
|
|
||||||
return Timestamp(unixTimeInMillis: Int64(date.iosDate.timeIntervalSince1970 * 1000))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromTimestamp(timestamp: Timestamp) -> LocalDate {
|
|
||||||
return Date.init(timeIntervalSince1970: Double(timestamp.unixTimeInMillis / 1000)).localDate
|
|
||||||
}
|
|
||||||
|
|
||||||
let calendar = Calendar(identifier: .gregorian)
|
|
||||||
|
|
||||||
func dayOfWeek(date: LocalDate) -> DayOfWeek {
|
|
||||||
let weekday = calendar.component(.weekday, from: date.iosDate)
|
|
||||||
switch(weekday) {
|
|
||||||
case 1: return DayOfWeek.sunday
|
|
||||||
case 2: return DayOfWeek.monday
|
|
||||||
case 3: return DayOfWeek.tuesday
|
|
||||||
case 4: return DayOfWeek.wednesday
|
|
||||||
case 5: return DayOfWeek.thursday
|
|
||||||
case 6: return DayOfWeek.friday
|
|
||||||
default: return DayOfWeek.saturday
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusDays(date: LocalDate, days: Int32) -> LocalDate {
|
|
||||||
let d2 = date.iosDate.addingTimeInterval(24.0 * 60 * 60 * Double(days))
|
|
||||||
return LocalDate(year: Int32(calendar.component(.year, from: d2)),
|
|
||||||
month: Int32(calendar.component(.month, from: d2)),
|
|
||||||
day: Int32(calendar.component(.day, from: d2)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
0057EC2B224C4CDB00C49288 /* icons in Resources */ = {isa = PBXBuildFile; fileRef = 0057EC2A224C4CDB00C49288 /* icons */; };
|
0057EC2B224C4CDB00C49288 /* icons in Resources */ = {isa = PBXBuildFile; fileRef = 0057EC2A224C4CDB00C49288 /* icons */; };
|
||||||
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
|
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
|
||||||
00A5B42A22009F590024E00C /* ListHabitsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* ListHabitsController.swift */; };
|
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* MainScreenController.swift */; };
|
||||||
00A5B42F22009F5A0024E00C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00A5B42E22009F5A0024E00C /* Assets.xcassets */; };
|
00A5B42F22009F5A0024E00C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00A5B42E22009F5A0024E00C /* Assets.xcassets */; };
|
||||||
00C0C6A52246537A003D8AF0 /* IosFilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A122465365003D8AF0 /* IosFilesTest.swift */; };
|
00C0C6A52246537A003D8AF0 /* IosFilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A122465365003D8AF0 /* IosFilesTest.swift */; };
|
||||||
00C0C6A62246537E003D8AF0 /* IosDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */; };
|
00C0C6A62246537E003D8AF0 /* IosDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */; };
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
0057EC2A224C4CDB00C49288 /* icons */ = {isa = PBXFileReference; lastKnownFileType = folder; path = icons; sourceTree = "<group>"; };
|
0057EC2A224C4CDB00C49288 /* icons */ = {isa = PBXFileReference; lastKnownFileType = folder; path = icons; sourceTree = "<group>"; };
|
||||||
00A5B42422009F590024E00C /* uhabits.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uhabits.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
00A5B42422009F590024E00C /* uhabits.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uhabits.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
00A5B42722009F590024E00C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
00A5B42722009F590024E00C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
00A5B42922009F590024E00C /* ListHabitsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHabitsController.swift; sourceTree = "<group>"; };
|
00A5B42922009F590024E00C /* MainScreenController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenController.swift; sourceTree = "<group>"; };
|
||||||
00A5B42E22009F5A0024E00C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
00A5B42E22009F5A0024E00C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
00A5B43322009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
00A5B43322009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
00A5B43822009F5A0024E00C /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
00A5B43822009F5A0024E00C /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
00D48BD22200AC1600CC4527 /* EditHabitController.swift */,
|
00D48BD22200AC1600CC4527 /* EditHabitController.swift */,
|
||||||
00A5B42922009F590024E00C /* ListHabitsController.swift */,
|
00A5B42922009F590024E00C /* MainScreenController.swift */,
|
||||||
00C0C6DE224A35FC003D8AF0 /* ShowHabitController.swift */,
|
00C0C6DE224A35FC003D8AF0 /* ShowHabitController.swift */,
|
||||||
);
|
);
|
||||||
path = Frontend;
|
path = Frontend;
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "cd ../core\n./gradlew linkDebugFrameworkIOS\n";
|
shellScript = "cd ../core\n./gradlew linkIOS\n";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@
|
|||||||
00C0C6E0224A3602003D8AF0 /* ShowHabitController.swift in Sources */,
|
00C0C6E0224A3602003D8AF0 /* ShowHabitController.swift in Sources */,
|
||||||
00C0C6A8224654A2003D8AF0 /* IosDatabase.swift in Sources */,
|
00C0C6A8224654A2003D8AF0 /* IosDatabase.swift in Sources */,
|
||||||
00C0C6DB2247E6B0003D8AF0 /* IosDates.swift in Sources */,
|
00C0C6DB2247E6B0003D8AF0 /* IosDates.swift in Sources */,
|
||||||
00A5B42A22009F590024E00C /* ListHabitsController.swift in Sources */,
|
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */,
|
||||||
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */,
|
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */,
|
||||||
00D48BD32200AC1600CC4527 /* EditHabitController.swift in Sources */,
|
00D48BD32200AC1600CC4527 /* EditHabitController.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
@@ -83,7 +83,8 @@
|
|||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<MacroExpansion>
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "00A5B42322009F590024E00C"
|
BlueprintIdentifier = "00A5B42322009F590024E00C"
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
BlueprintName = "uhabits"
|
BlueprintName = "uhabits"
|
||||||
ReferencedContainer = "container:uhabits.xcodeproj">
|
ReferencedContainer = "container:uhabits.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
|
|||||||
Reference in New Issue
Block a user