After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.concurrency
|
||||
|
||||
/**
|
||||
* A TaskRunner provides the ability of running tasks in different queues. The
|
||||
* class is also observable, and notifies listeners when new tasks are started
|
||||
* or finished.
|
||||
*
|
||||
* Two queues are available: a foreground queue and a background queue. These
|
||||
* two queues may run in parallel, depending on the hardware. Multiple tasks
|
||||
* submitted to the same queue, however, always run sequentially, in the order
|
||||
* they were enqueued.
|
||||
*/
|
||||
interface TaskRunner {
|
||||
|
||||
val listeners: MutableList<Listener>
|
||||
|
||||
val activeTaskCount: Int
|
||||
|
||||
fun runInBackground(task: () -> Unit)
|
||||
|
||||
fun runInForeground(task: () -> Unit)
|
||||
|
||||
interface Listener {
|
||||
fun onTaskStarted()
|
||||
fun onTaskFinished()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequential implementation of TaskRunner. Both background and foreground
|
||||
* queues run in the same thread, so they block each other.
|
||||
*/
|
||||
class SequentialTaskRunner : TaskRunner {
|
||||
|
||||
override val listeners = mutableListOf<TaskRunner.Listener>()
|
||||
|
||||
override var activeTaskCount = 0
|
||||
|
||||
override fun runInBackground(task: () -> Unit) {
|
||||
activeTaskCount += 1
|
||||
for (l in listeners) l.onTaskStarted()
|
||||
task()
|
||||
activeTaskCount -= 1
|
||||
for (l in listeners) l.onTaskFinished()
|
||||
}
|
||||
|
||||
override fun runInForeground(task: () -> Unit) = runInBackground(task)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.gui
|
||||
|
||||
data class PaletteColor(val index: Int)
|
||||
|
||||
data class Color(val red: Double,
|
||||
val green: Double,
|
||||
val blue: Double,
|
||||
val alpha: Double) {
|
||||
|
||||
val luminosity: Double
|
||||
get() {
|
||||
return 0.21 * red + 0.72 * green + 0.07 * blue
|
||||
}
|
||||
|
||||
constructor(rgb: Int) : this(((rgb shr 16) and 0xFF) / 255.0,
|
||||
((rgb shr 8) and 0xFF) / 255.0,
|
||||
((rgb shr 0) and 0xFF) / 255.0,
|
||||
1.0)
|
||||
|
||||
fun blendWith(other: Color, weight: Double): Color {
|
||||
return Color(red * (1 - weight) + other.red * weight,
|
||||
green * (1 - weight) + other.green * weight,
|
||||
blue * (1 - weight) + other.blue * weight,
|
||||
alpha * (1 - weight) + other.alpha * weight)
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits
|
||||
|
||||
import org.isoron.uhabits.gui.*
|
||||
import org.isoron.uhabits.models.*
|
||||
import org.isoron.uhabits.utils.*
|
||||
|
||||
|
||||
class Backend(databaseOpener: DatabaseOpener,
|
||||
fileOpener: FileOpener,
|
||||
log: Log) {
|
||||
|
||||
private var database: Database
|
||||
private var habitsRepository: HabitRepository
|
||||
private var habits = mutableMapOf<Int, Habit>()
|
||||
var theme: Theme = LightTheme()
|
||||
|
||||
init {
|
||||
val dbFile = fileOpener.openUserFile("uhabits.db")
|
||||
database = databaseOpener.open(dbFile)
|
||||
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||
habitsRepository = HabitRepository(database)
|
||||
habits = habitsRepository.findAll()
|
||||
}
|
||||
|
||||
fun getHabitList(): List<Map<String, *>> {
|
||||
return habits.values
|
||||
.filter { h -> !h.isArchived }
|
||||
.sortedBy { h -> h.position }
|
||||
.map { h ->
|
||||
mapOf("key" to h.id.toString(),
|
||||
"name" to h.name,
|
||||
"color" to h.color.index)
|
||||
}
|
||||
}
|
||||
|
||||
fun createHabit(name: String) {
|
||||
val id = habitsRepository.nextId()
|
||||
val habit = Habit(id = id,
|
||||
name = name,
|
||||
description = "",
|
||||
frequency = Frequency(1, 1),
|
||||
color = PaletteColor(3),
|
||||
isArchived = false,
|
||||
position = habits.size,
|
||||
unit = "",
|
||||
target = 0.0,
|
||||
type = HabitType.YES_NO_HABIT)
|
||||
habitsRepository.insert(habit)
|
||||
habits[id] = habit
|
||||
}
|
||||
|
||||
fun deleteHabit(id: Int) {
|
||||
val habit = habits[id]!!
|
||||
habitsRepository.delete(habit)
|
||||
habits.remove(id)
|
||||
}
|
||||
|
||||
fun updateHabit(id: Int, name: String) {
|
||||
val habit = habits[id]!!
|
||||
habit.name = name
|
||||
habitsRepository.update(habit)
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.backend
|
||||
|
||||
import org.isoron.platform.concurrency.*
|
||||
import org.isoron.platform.io.*
|
||||
import org.isoron.platform.time.*
|
||||
import org.isoron.uhabits.*
|
||||
import org.isoron.uhabits.components.*
|
||||
import org.isoron.uhabits.models.*
|
||||
|
||||
class Backend(databaseName: String,
|
||||
databaseOpener: DatabaseOpener,
|
||||
fileOpener: FileOpener,
|
||||
val log: Log,
|
||||
val dateCalculator: LocalDateCalculator,
|
||||
val taskRunner: TaskRunner) {
|
||||
|
||||
val database: Database
|
||||
|
||||
val habitsRepository: HabitRepository
|
||||
|
||||
val checkmarkRepository: CheckmarkRepository
|
||||
|
||||
val habits = mutableMapOf<Int, Habit>()
|
||||
|
||||
val checkmarks = mutableMapOf<Habit, CheckmarkList>()
|
||||
|
||||
val mainScreenDataSource: MainScreenDataSource
|
||||
|
||||
var theme: Theme = LightTheme()
|
||||
|
||||
init {
|
||||
val dbFile = fileOpener.openUserFile(databaseName)
|
||||
if (!dbFile.exists()) {
|
||||
val templateFile = fileOpener.openResourceFile("databases/template.db")
|
||||
templateFile.copyTo(dbFile)
|
||||
}
|
||||
database = databaseOpener.open(dbFile)
|
||||
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
|
||||
habitsRepository = HabitRepository(database)
|
||||
checkmarkRepository = CheckmarkRepository(database, dateCalculator)
|
||||
taskRunner.runInBackground {
|
||||
habits.putAll(habitsRepository.findAll())
|
||||
for ((key, habit) in habits) {
|
||||
val checks = checkmarkRepository.findAll(key)
|
||||
checkmarks[habit] = CheckmarkList(habit.frequency,
|
||||
dateCalculator)
|
||||
checkmarks[habit]?.setManualCheckmarks(checks)
|
||||
}
|
||||
}
|
||||
mainScreenDataSource = MainScreenDataSource(habits,
|
||||
checkmarks,
|
||||
taskRunner)
|
||||
}
|
||||
|
||||
fun createHabit(habit: Habit) {
|
||||
val id = habitsRepository.nextId()
|
||||
habit.id = id
|
||||
habit.position = habits.size
|
||||
habits[id] = habit
|
||||
checkmarks[habit] = CheckmarkList(habit.frequency, dateCalculator)
|
||||
habitsRepository.insert(habit)
|
||||
mainScreenDataSource.requestData()
|
||||
}
|
||||
|
||||
fun deleteHabit(id: Int) {
|
||||
habits[id]?.let { habit ->
|
||||
habitsRepository.delete(habit)
|
||||
habits.remove(id)
|
||||
mainScreenDataSource.requestData()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateHabit(modified: Habit) {
|
||||
habits[modified.id]?.let { existing ->
|
||||
modified.position = existing.position
|
||||
habitsRepository.update(modified)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.backend
|
||||
|
||||
import org.isoron.platform.concurrency.*
|
||||
import org.isoron.platform.gui.*
|
||||
import org.isoron.platform.time.*
|
||||
import org.isoron.uhabits.models.*
|
||||
|
||||
class MainScreenDataSource(val habits: MutableMap<Int, Habit>,
|
||||
val checkmarks: MutableMap<Habit, CheckmarkList>,
|
||||
val taskRunner: TaskRunner) {
|
||||
|
||||
private val today = LocalDate(2019, 3, 30)
|
||||
|
||||
data class Data(val ids: List<Int>,
|
||||
val scores: List<Double>,
|
||||
val names: List<String>,
|
||||
val colors: List<PaletteColor>,
|
||||
val checkmarks: List<List<Int>>)
|
||||
|
||||
private val listeners = mutableListOf<Listener>()
|
||||
|
||||
fun addListener(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeListener(listener: Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onDataChanged(newData: Data)
|
||||
}
|
||||
|
||||
fun requestData() {
|
||||
taskRunner.runInBackground {
|
||||
val filteredHabits = habits.values.filter { h -> !h.isArchived }
|
||||
val ids = filteredHabits.map { it.id }
|
||||
val scores = filteredHabits.map { 0.0 }
|
||||
val names = filteredHabits.map { it.name }
|
||||
val colors = filteredHabits.map { it.color }
|
||||
val ck = filteredHabits.map { habit ->
|
||||
val allValues = checkmarks[habit]!!.getValuesUntil(today)
|
||||
if (allValues.size <= 7) allValues
|
||||
else allValues.subList(0, 7)
|
||||
}
|
||||
val data = Data(ids, scores, names, colors, ck)
|
||||
taskRunner.runInForeground {
|
||||
listeners.forEach { listener ->
|
||||
listener.onDataChanged(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.components
|
||||
|
||||
import org.isoron.platform.gui.*
|
||||
import org.isoron.platform.time.*
|
||||
import kotlin.math.*
|
||||
|
||||
class CalendarChart(var today: LocalDate,
|
||||
var color: Color,
|
||||
var theme: Theme,
|
||||
var dateCalculator: LocalDateCalculator,
|
||||
var dateFormatter: LocalDateFormatter) : Component {
|
||||
|
||||
var padding = 5.0
|
||||
var backgroundColor = Color(0xFFFFFF)
|
||||
var squareSpacing = 1.0
|
||||
var series = listOf<Double>()
|
||||
var scrollPosition = 0
|
||||
|
||||
private var squareSize = 0.0
|
||||
private var fontSize = 0.0
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
val width = canvas.getWidth()
|
||||
val height = canvas.getHeight()
|
||||
canvas.setColor(backgroundColor)
|
||||
canvas.fillRect(0.0, 0.0, width, height)
|
||||
squareSize = round((height - 2 * padding) / 8.0)
|
||||
canvas.setFontSize(height * 0.06)
|
||||
|
||||
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
|
||||
val todayWeekday = dateCalculator.dayOfWeek(today)
|
||||
val topLeftOffset = (nColumns - 1 + scrollPosition) * 7 + todayWeekday.index
|
||||
val topLeftDate = dateCalculator.minusDays(today, topLeftOffset)
|
||||
|
||||
repeat(nColumns) { column ->
|
||||
val topOffset = topLeftOffset - 7 * column
|
||||
val topDate = dateCalculator.plusDays(topLeftDate, 7 * column)
|
||||
drawColumn(canvas, column, topDate, topOffset)
|
||||
}
|
||||
|
||||
canvas.setColor(theme.mediumContrastTextColor)
|
||||
repeat(7) { row ->
|
||||
val date = dateCalculator.plusDays(topLeftDate, row)
|
||||
canvas.setTextAlign(TextAlign.LEFT)
|
||||
canvas.drawText(dateFormatter.shortWeekdayName(date),
|
||||
padding + nColumns * squareSize + padding,
|
||||
padding + squareSize * (row+1) + squareSize / 2)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawColumn(canvas: Canvas,
|
||||
column: Int,
|
||||
topDate: LocalDate,
|
||||
topOffset: Int) {
|
||||
drawHeader(canvas, column, topDate)
|
||||
repeat(7) { row ->
|
||||
val offset = topOffset - row
|
||||
val date = dateCalculator.plusDays(topDate, row)
|
||||
if (offset < 0) return
|
||||
drawSquare(canvas,
|
||||
padding + column * squareSize,
|
||||
padding + (row + 1) * squareSize,
|
||||
squareSize - squareSpacing,
|
||||
squareSize - squareSpacing,
|
||||
date,
|
||||
offset)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawHeader(canvas: Canvas, column: Int, date: LocalDate) {
|
||||
if (date.day >= 8) return
|
||||
|
||||
canvas.setColor(theme.mediumContrastTextColor)
|
||||
if (date.month == 1) {
|
||||
canvas.drawText(date.year.toString(),
|
||||
padding + column * squareSize + squareSize / 2,
|
||||
padding + squareSize / 2)
|
||||
|
||||
} else {
|
||||
canvas.drawText(dateFormatter.shortMonthName(date),
|
||||
padding + column * squareSize + squareSize / 2,
|
||||
padding + squareSize / 2)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawSquare(canvas: Canvas,
|
||||
x: Double,
|
||||
y: Double,
|
||||
width: Double,
|
||||
height: Double,
|
||||
date: LocalDate,
|
||||
offset: Int) {
|
||||
|
||||
var value = if (offset >= series.size) 0.0 else series[offset]
|
||||
value = round(value * 5.0) / 5.0
|
||||
|
||||
var squareColor = color.blendWith(backgroundColor, 1 - value)
|
||||
var textColor = backgroundColor
|
||||
|
||||
if (value == 0.0) squareColor = theme.lowContrastTextColor
|
||||
if (squareColor.luminosity > 0.8)
|
||||
textColor = squareColor.blendWith(theme.highContrastTextColor, 0.5)
|
||||
|
||||
canvas.setColor(squareColor)
|
||||
canvas.fillRect(x, y, width, height)
|
||||
canvas.setColor(textColor)
|
||||
canvas.drawText(date.day.toString(), x + width / 2, y + width / 2)
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.gui
|
||||
|
||||
data class PaletteColor(val index: Int)
|
||||
|
||||
class Color(val argb: Int) {
|
||||
val alpha = 1.0
|
||||
val red = ((argb shr 16) and 0xFF) / 255.0
|
||||
val green = ((argb shr 8 ) and 0xFF) / 255.0
|
||||
val blue = ((argb shr 0 ) and 0xFF) / 255.0
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models
|
||||
|
||||
import org.isoron.platform.time.*
|
||||
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.UNCHECKED
|
||||
|
||||
class CheckmarkList(private val frequency: Frequency,
|
||||
private val dateCalculator: LocalDateCalculator) {
|
||||
|
||||
private val manualCheckmarks = mutableListOf<Checkmark>()
|
||||
private val automaticCheckmarks = mutableListOf<Checkmark>()
|
||||
|
||||
/**
|
||||
* Replaces the entire list of manual checkmarks by the ones provided. The
|
||||
* list of automatic checkmarks will be automatically updated.
|
||||
*/
|
||||
fun setManualCheckmarks(checks: List<Checkmark>) {
|
||||
manualCheckmarks.clear()
|
||||
automaticCheckmarks.clear()
|
||||
manualCheckmarks.addAll(checks)
|
||||
automaticCheckmarks.addAll(computeAutomaticCheckmarks(checks,
|
||||
frequency,
|
||||
dateCalculator))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns values of all checkmarks (manual and automatic) from the oldest
|
||||
* entry until the date provided.
|
||||
*
|
||||
* The interval is inclusive, and the list is sorted from newest to oldest.
|
||||
* That is, the first element of the returned list corresponds to the date
|
||||
* provided.
|
||||
*/
|
||||
fun getValuesUntil(date: LocalDate): List<Int> {
|
||||
if (automaticCheckmarks.isEmpty()) return listOf()
|
||||
|
||||
val result = mutableListOf<Int>()
|
||||
val newest = automaticCheckmarks.first().date
|
||||
val distToNewest = dateCalculator.distanceInDays(newest, date)
|
||||
|
||||
var fromIndex = 0
|
||||
val toIndex = automaticCheckmarks.size
|
||||
if (newest.isOlderThan(date)) {
|
||||
repeat(distToNewest) { result.add(UNCHECKED) }
|
||||
} else {
|
||||
fromIndex = distToNewest
|
||||
}
|
||||
val subList = automaticCheckmarks.subList(fromIndex, toIndex)
|
||||
result.addAll(subList.map { it.value })
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Computes the list of automatic checkmarks a list of manual ones.
|
||||
*/
|
||||
fun computeAutomaticCheckmarks(checks: List<Checkmark>,
|
||||
frequency: Frequency,
|
||||
calc: LocalDateCalculator
|
||||
): MutableList<Checkmark> {
|
||||
|
||||
val intervals = buildIntervals(checks, frequency, calc)
|
||||
snapIntervalsTogether(intervals, calc)
|
||||
return buildCheckmarksFromIntervals(checks, intervals, calc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the intervals so that gaps between intervals are eliminated.
|
||||
*
|
||||
* More specifically, this function shifts the beginning and the end of
|
||||
* intervals so that they overlap the least as possible. The center of
|
||||
* the interval, however, still falls within the interval. The length of
|
||||
* the intervals are also not modified.
|
||||
*/
|
||||
fun snapIntervalsTogether(intervals: MutableList<Interval>,
|
||||
calc: LocalDateCalculator) {
|
||||
|
||||
for (i in 1 until intervals.size) {
|
||||
val (begin, center, end) = intervals[i]
|
||||
val (_, _, prevEnd) = intervals[i - 1]
|
||||
|
||||
val gap = calc.distanceInDays(prevEnd, begin) - 1
|
||||
val endMinusGap = calc.minusDays(end, gap)
|
||||
if (gap <= 0 || endMinusGap.isOlderThan(center)) continue
|
||||
intervals[i] = Interval(calc.minusDays(begin, gap),
|
||||
center,
|
||||
calc.minusDays(end, gap))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of (manually checked) checkmarks and computed
|
||||
* intervals into a list of unchecked, manually checked and
|
||||
* automatically checked checkmarks.
|
||||
*
|
||||
* Manual checkmarks are simply copied over to the output list. Days
|
||||
* that are an interval, but which do not have manual checkmarks receive
|
||||
* automatic checkmarks. Days that fall in the gaps between intervals
|
||||
* receive unchecked checkmarks.
|
||||
*/
|
||||
fun buildCheckmarksFromIntervals(checks: List<Checkmark>,
|
||||
intervals: List<Interval>,
|
||||
calc: LocalDateCalculator
|
||||
): MutableList<Checkmark> {
|
||||
|
||||
if (checks.isEmpty()) throw IllegalArgumentException()
|
||||
if (intervals.isEmpty()) throw IllegalArgumentException()
|
||||
|
||||
var oldest = intervals[0].begin
|
||||
var newest = intervals[0].end
|
||||
for (interval in intervals) {
|
||||
if (interval.begin.isOlderThan(oldest)) oldest = interval.begin
|
||||
if (interval.end.isNewerThan(newest)) newest = interval.end
|
||||
}
|
||||
for (check in checks) {
|
||||
if (check.date.isOlderThan(oldest)) oldest = check.date
|
||||
if (check.date.isNewerThan(newest)) newest = check.date
|
||||
}
|
||||
|
||||
val distance = calc.distanceInDays(oldest, newest)
|
||||
val checkmarks = mutableListOf<Checkmark>()
|
||||
for (offset in 0..distance)
|
||||
checkmarks.add(Checkmark(calc.minusDays(newest, offset),
|
||||
UNCHECKED))
|
||||
|
||||
for (interval in intervals) {
|
||||
val beginOffset = calc.distanceInDays(newest, interval.begin)
|
||||
val endOffset = calc.distanceInDays(newest, interval.end)
|
||||
|
||||
for (offset in endOffset..beginOffset) {
|
||||
checkmarks.set(offset,
|
||||
Checkmark(calc.minusDays(newest, offset),
|
||||
CHECKED_AUTOMATIC))
|
||||
}
|
||||
}
|
||||
|
||||
for (check in checks) {
|
||||
val offset = calc.distanceInDays(newest, check.date)
|
||||
checkmarks.set(offset, Checkmark(check.date, CHECKED_MANUAL))
|
||||
}
|
||||
|
||||
return checkmarks
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a list of intervals based on a list of (manual)
|
||||
* checkmarks.
|
||||
*/
|
||||
fun buildIntervals(checks: List<Checkmark>,
|
||||
frequency: Frequency,
|
||||
calc: LocalDateCalculator): MutableList<Interval> {
|
||||
|
||||
val num = frequency.numerator
|
||||
val den = frequency.denominator
|
||||
|
||||
val intervals = mutableListOf<Interval>()
|
||||
for (i in 0..(checks.size - num)) {
|
||||
val first = checks[i]
|
||||
val last = checks[i + num - 1]
|
||||
|
||||
val distance = calc.distanceInDays(first.date, last.date)
|
||||
if (distance >= den) continue
|
||||
|
||||
val end = calc.plusDays(first.date, den - 1)
|
||||
intervals.add(Interval(first.date, last.date, end))
|
||||
}
|
||||
|
||||
return intervals
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For non-daily habits, some groups of repetitions generate many
|
||||
* automatic checkmarks. For weekly habits, each repetition generates
|
||||
* seven checkmarks. For twice-a-week habits, two repetitions that are close
|
||||
* enough together also generate seven checkmarks. This group of generated
|
||||
* checkmarks is represented by an interval.
|
||||
*
|
||||
* The fields `begin` and `end` indicate the length of the interval, and are
|
||||
* inclusive. The field `center` indicates the newest day within the interval
|
||||
* that has a manual checkmark.
|
||||
*/
|
||||
data class Interval(val begin: LocalDate,
|
||||
val center: LocalDate,
|
||||
val end: LocalDate)
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models
|
||||
|
||||
import org.isoron.platform.io.*
|
||||
import org.isoron.platform.time.*
|
||||
|
||||
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 insertStatement = db.prepareStatement("insert into Repetitions(habit, timestamp, value) values (?, ?, ?)")
|
||||
private val deleteStatement = db.prepareStatement("delete from Repetitions where habit=? and timestamp=?")
|
||||
|
||||
fun findAll(habitId: Int): List<Checkmark> {
|
||||
findStatement.bindInt(0, habitId)
|
||||
val result = mutableListOf<Checkmark>()
|
||||
while (findStatement.step() == StepResult.ROW) {
|
||||
val timestamp = Timestamp(findStatement.getLong(0))
|
||||
val value = findStatement.getInt(1)
|
||||
val date = dateCalculator.fromTimestamp(timestamp)
|
||||
result.add(Checkmark(date, value))
|
||||
}
|
||||
findStatement.reset()
|
||||
return result
|
||||
}
|
||||
|
||||
fun insert(habitId: Int, checkmark: Checkmark) {
|
||||
val timestamp = dateCalculator.toTimestamp(checkmark.date)
|
||||
insertStatement.bindInt(0, habitId)
|
||||
insertStatement.bindLong(1, timestamp.unixTimeInMillis)
|
||||
insertStatement.bindInt(2, checkmark.value)
|
||||
insertStatement.step()
|
||||
insertStatement.reset()
|
||||
}
|
||||
|
||||
fun delete(habitId: Int, date: LocalDate) {
|
||||
val timestamp = dateCalculator.toTimestamp(date)
|
||||
deleteStatement.bindInt(0, habitId)
|
||||
deleteStatement.bindLong(1, timestamp.unixTimeInMillis)
|
||||
deleteStatement.step()
|
||||
deleteStatement.reset()
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.time
|
||||
|
||||
import java.lang.Math.*
|
||||
import java.util.*
|
||||
import java.util.Calendar.*
|
||||
|
||||
fun LocalDate.toGregorianCalendar(): GregorianCalendar {
|
||||
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0)
|
||||
cal.set(Calendar.MINUTE, 0)
|
||||
cal.set(Calendar.SECOND, 0)
|
||||
cal.set(Calendar.MILLISECOND, 0)
|
||||
cal.set(Calendar.YEAR, this.year)
|
||||
cal.set(Calendar.MONTH, this.month - 1)
|
||||
cal.set(Calendar.DAY_OF_MONTH, this.day)
|
||||
return cal
|
||||
}
|
||||
|
||||
fun GregorianCalendar.toLocalDate(): LocalDate {
|
||||
return LocalDate(this.get(YEAR),
|
||||
this.get(MONTH) + 1,
|
||||
this.get(DAY_OF_MONTH))
|
||||
}
|
||||
|
||||
class JavaLocalDateFormatter(private val locale: Locale) : LocalDateFormatter {
|
||||
override fun shortMonthName(date: LocalDate): String {
|
||||
val cal = date.toGregorianCalendar()
|
||||
val longName = cal.getDisplayName(MONTH, LONG, locale)
|
||||
val shortName = cal.getDisplayName(MONTH, SHORT, locale)
|
||||
|
||||
// For some locales, such as Japan, SHORT name is exceedingly short
|
||||
return if (longName.length <= 3) longName else shortName
|
||||
}
|
||||
|
||||
override fun shortWeekdayName(date: LocalDate): String {
|
||||
val cal = date.toGregorianCalendar()
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.utils
|
||||
|
||||
import java.util.*
|
||||
import java.util.Calendar.*
|
||||
|
||||
class JavaLocalDateFormatter(private val locale: Locale) : LocalDateFormatter {
|
||||
override fun shortWeekdayName(date: LocalDate): String {
|
||||
val d = GregorianCalendar(date.year, date.month - 1, date.day)
|
||||
return d.getDisplayName(DAY_OF_WEEK, SHORT, locale);
|
||||
}
|
||||
}
|
||||
|
||||
class JavaLocalDateCalculator : LocalDateCalculator {
|
||||
override fun plusDays(date: LocalDate, days: Int): LocalDate {
|
||||
val d = GregorianCalendar(date.year, date.month - 1, date.day)
|
||||
d.add(Calendar.DAY_OF_MONTH, days)
|
||||
return LocalDate(d.get(Calendar.YEAR),
|
||||
d.get(Calendar.MONTH) + 1,
|
||||
d.get(Calendar.DAY_OF_MONTH))
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform
|
||||
|
||||
import junit.framework.TestCase.*
|
||||
import org.isoron.platform.time.*
|
||||
import org.junit.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
class JavaDatesTest {
|
||||
private val calc = JavaLocalDateCalculator()
|
||||
private val d1 = LocalDate(2019, 3, 25)
|
||||
private val d2 = LocalDate(2019, 4, 4)
|
||||
private val d3 = LocalDate(2019, 5, 12)
|
||||
|
||||
@Test
|
||||
fun plusMinusDays() {
|
||||
val today = LocalDate(2019, 3, 25)
|
||||
assertEquals(calc.minusDays(today, 28), LocalDate(2019, 2, 25))
|
||||
assertEquals(calc.plusDays(today, 7), LocalDate(2019, 4, 1))
|
||||
assertEquals(calc.plusDays(today, 42), LocalDate(2019, 5, 6))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortMonthName() {
|
||||
var fmt = JavaLocalDateFormatter(Locale.US)
|
||||
assertEquals(fmt.shortWeekdayName(d1), "Mon")
|
||||
assertEquals(fmt.shortWeekdayName(d2), "Thu")
|
||||
assertEquals(fmt.shortWeekdayName(d3), "Sun")
|
||||
assertEquals(fmt.shortMonthName(d1), "Mar")
|
||||
assertEquals(fmt.shortMonthName(d2), "Apr")
|
||||
assertEquals(fmt.shortMonthName(d3), "May")
|
||||
|
||||
fmt = JavaLocalDateFormatter(Locale.JAPAN)
|
||||
assertEquals(fmt.shortWeekdayName(d1), "月")
|
||||
assertEquals(fmt.shortWeekdayName(d2), "木")
|
||||
assertEquals(fmt.shortWeekdayName(d3), "日")
|
||||
assertEquals(fmt.shortMonthName(d1), "3月")
|
||||
assertEquals(fmt.shortMonthName(d2), "4月")
|
||||
assertEquals(fmt.shortMonthName(d3), "5月")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun weekDay() {
|
||||
assertEquals(DayOfWeek.SUNDAY, calc.dayOfWeek(LocalDate(2015, 1, 25)))
|
||||
assertEquals(DayOfWeek.MONDAY, calc.dayOfWeek(LocalDate(2017, 7, 3)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun timestamps() {
|
||||
val timestamps = listOf(Timestamp(1555977600000),
|
||||
Timestamp(968716800000),
|
||||
Timestamp(0))
|
||||
val dates = listOf(LocalDate(2019, 4, 23),
|
||||
LocalDate(2000, 9, 12),
|
||||
LocalDate(1970, 1, 1))
|
||||
assertEquals(timestamps, dates.map { d -> calc.toTimestamp(d) })
|
||||
assertEquals(dates, timestamps.map { t -> calc.fromTimestamp(t) })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOlderThan() {
|
||||
val ref = LocalDate(2010, 10, 5)
|
||||
assertTrue(ref.isOlderThan(LocalDate(2010, 10, 10)))
|
||||
assertTrue(ref.isOlderThan(LocalDate(2010, 11, 4)))
|
||||
assertTrue(ref.isOlderThan(LocalDate(2011, 1, 5)))
|
||||
assertTrue(ref.isOlderThan(LocalDate(2015, 3, 1)))
|
||||
|
||||
assertFalse(ref.isOlderThan(LocalDate(2010, 10, 5)))
|
||||
assertFalse(ref.isOlderThan(LocalDate(2010, 10, 4)))
|
||||
assertFalse(ref.isOlderThan(LocalDate(2010, 9, 1)))
|
||||
assertFalse(ref.isOlderThan(LocalDate(2005, 10, 5)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDistanceInDays() {
|
||||
val d1 = LocalDate(2019, 5, 10)
|
||||
val d2 = LocalDate(2019, 5, 30)
|
||||
val d3 = LocalDate(2019, 6, 5)
|
||||
|
||||
assertEquals(0, calc.distanceInDays(d1, d1))
|
||||
assertEquals(20, calc.distanceInDays(d1, d2))
|
||||
assertEquals(20, calc.distanceInDays(d2, d1))
|
||||
assertEquals(26, calc.distanceInDays(d1, d3))
|
||||
assertEquals(6, calc.distanceInDays(d2, d3))
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits
|
||||
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class BackendTest : BaseTest() {
|
||||
@Test
|
||||
fun testBackend() {
|
||||
// val backend = Backend(databaseOpener, fileOpener)
|
||||
// assertEquals(backend.getHabitList().size, 0)
|
||||
//
|
||||
// backend.createHabit("Brush teeth")
|
||||
// backend.createHabit("Wake up early")
|
||||
|
||||
// var result = backend.getHabitList()
|
||||
// assertEquals(2, result.size)
|
||||
// assertEquals(result[0]["name"], "Brush teeth")
|
||||
// assertEquals(result[0]["name"], "Wake up early")
|
||||
//
|
||||
// backend.deleteHabit(1)
|
||||
// result = backend.getHabitList()
|
||||
// assertEquals(result.size, 1)
|
||||
//
|
||||
// backend.updateHabit(2, "Wake up late")
|
||||
// result = backend.getHabitList()
|
||||
// assertEquals(result[2]["name"], "Wake up late")
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.backend
|
||||
|
||||
import junit.framework.TestCase.*
|
||||
import org.isoron.platform.gui.*
|
||||
import org.isoron.uhabits.*
|
||||
import org.junit.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
class BackendTest : BaseTest() {
|
||||
lateinit var backend: Backend
|
||||
private val latch = CountDownLatch(1)
|
||||
val dbFilename = "uhabits${Random().nextInt()}.db"
|
||||
val dbFile = fileOpener.openUserFile(dbFilename)
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
if (dbFile.exists()) dbFile.delete()
|
||||
backend = Backend(dbFilename,
|
||||
databaseOpener,
|
||||
fileOpener,
|
||||
log,
|
||||
dateCalculator,
|
||||
taskRunner)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
dbFile.delete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMainScreenDataSource() {
|
||||
val listener = object : MainScreenDataSource.Listener {
|
||||
override fun onDataChanged(newData: MainScreenDataSource.Data) {
|
||||
val expected = MainScreenDataSource.Data(
|
||||
ids = listOf(0, 10, 9, 2, 3, 4, 5, 11, 6, 7, 8),
|
||||
scores = listOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0),
|
||||
names = listOf("Wake up early", "Eat healthy", "Floss",
|
||||
"Journal", "Track time", "Meditate",
|
||||
"Work out", "Take a walk", "Read books",
|
||||
"Learn French", "Play chess"),
|
||||
colors = listOf(PaletteColor(8), PaletteColor(8),
|
||||
PaletteColor(8), PaletteColor(11),
|
||||
PaletteColor(11), PaletteColor(15),
|
||||
PaletteColor(15), PaletteColor(15),
|
||||
PaletteColor(2), PaletteColor(2),
|
||||
PaletteColor(13)),
|
||||
checkmarks = listOf(
|
||||
listOf(2, 0, 0, 0, 0, 2, 0),
|
||||
listOf(0, 2, 2, 2, 2, 2, 0),
|
||||
listOf(0, 0, 0, 0, 2, 0, 0),
|
||||
listOf(0, 2, 0, 2, 0, 0, 0),
|
||||
listOf(2, 2, 2, 0, 2, 2, 2),
|
||||
listOf(2, 1, 1, 2, 1, 2, 2),
|
||||
listOf(2, 0, 2, 0, 2, 1, 2),
|
||||
listOf(0, 2, 2, 2, 2, 0, 0),
|
||||
listOf(0, 2, 2, 2, 2, 2, 0),
|
||||
listOf(0, 0, 2, 0, 2, 0, 2),
|
||||
listOf(0, 2, 0, 0, 2, 2, 0)))
|
||||
assertEquals(newData, expected)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
backend.mainScreenDataSource.addListener(listener)
|
||||
backend.mainScreenDataSource.requestData()
|
||||
assertTrue(latch.await(3, TimeUnit.SECONDS))
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.components
|
||||
|
||||
import org.isoron.platform.time.*
|
||||
import org.isoron.uhabits.*
|
||||
import org.junit.*
|
||||
import java.util.*
|
||||
|
||||
class CalendarChartTest : BaseViewTest() {
|
||||
val base = "components/CalendarChart"
|
||||
|
||||
@Test
|
||||
fun testDraw() {
|
||||
val component = CalendarChart(LocalDate(2015, 1, 25),
|
||||
theme.color(4),
|
||||
theme,
|
||||
JavaLocalDateCalculator(),
|
||||
JavaLocalDateFormatter(Locale.US))
|
||||
component.series = listOf(1.0, // today
|
||||
0.2, 0.5, 0.7, 0.0, 0.3, 0.4, 0.6,
|
||||
0.6, 0.0, 0.3, 0.6, 0.5, 0.8, 0.0,
|
||||
0.0, 0.0, 0.0, 0.6, 0.5, 0.7, 0.7,
|
||||
0.5, 0.5, 0.8, 0.9, 1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.2)
|
||||
assertRenders(800, 400, "$base/base.png", component)
|
||||
|
||||
component.scrollPosition = 2
|
||||
assertRenders(800, 400, "$base/scroll.png", component)
|
||||
|
||||
component.dateFormatter = JavaLocalDateFormatter(Locale.JAPAN)
|
||||
assertRenders(800, 400, "$base/base-jp.png", component)
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models
|
||||
|
||||
import org.isoron.platform.time.*
|
||||
import org.isoron.uhabits.*
|
||||
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.UNCHECKED
|
||||
import org.junit.Test
|
||||
import kotlin.test.*
|
||||
|
||||
class CheckmarkListTest : BaseTest() {
|
||||
|
||||
private val today = LocalDate(2019, 1, 30)
|
||||
|
||||
private fun day(offset: Int): LocalDate {
|
||||
return dateCalculator.minusDays(today, offset)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildIntervalsWeekly() {
|
||||
val checks = listOf(Checkmark(day(23), CHECKED_MANUAL),
|
||||
Checkmark(day(18), CHECKED_MANUAL),
|
||||
Checkmark(day(8), CHECKED_MANUAL))
|
||||
val expected = listOf(
|
||||
CheckmarkList.Interval(day(23), day(23), day(17)),
|
||||
CheckmarkList.Interval(day(18), day(18), day(12)),
|
||||
CheckmarkList.Interval(day(8), day(8), day(2)))
|
||||
val actual = CheckmarkList.buildIntervals(checks,
|
||||
Frequency.WEEKLY,
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildIntervalsDaily() {
|
||||
val checks = listOf(Checkmark(day(23), CHECKED_MANUAL),
|
||||
Checkmark(day(18), CHECKED_MANUAL),
|
||||
Checkmark(day(8), CHECKED_MANUAL))
|
||||
val expected = listOf(
|
||||
CheckmarkList.Interval(day(23), day(23), day(23)),
|
||||
CheckmarkList.Interval(day(18), day(18), day(18)),
|
||||
CheckmarkList.Interval(day(8), day(8), day(8)))
|
||||
val actual = CheckmarkList.buildIntervals(checks,
|
||||
Frequency.DAILY,
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildIntervalsTwoPerWeek() {
|
||||
val checks = listOf(Checkmark(day(23), CHECKED_MANUAL),
|
||||
Checkmark(day(22), CHECKED_MANUAL),
|
||||
Checkmark(day(18), CHECKED_MANUAL),
|
||||
Checkmark(day(15), CHECKED_MANUAL),
|
||||
Checkmark(day(8), CHECKED_MANUAL))
|
||||
val expected = listOf(
|
||||
CheckmarkList.Interval(day(23), day(22), day(17)),
|
||||
CheckmarkList.Interval(day(22), day(18), day(16)),
|
||||
CheckmarkList.Interval(day(18), day(15), day(12)))
|
||||
val actual = CheckmarkList.buildIntervals(checks,
|
||||
Frequency.TWO_TIMES_PER_WEEK,
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnapIntervalsTogether() {
|
||||
val original = mutableListOf(
|
||||
CheckmarkList.Interval(day(40), day(40), day(34)),
|
||||
CheckmarkList.Interval(day(25), day(25), day(19)),
|
||||
CheckmarkList.Interval(day(16), day(16), day(10)),
|
||||
CheckmarkList.Interval(day(8), day(8), day(2)))
|
||||
val expected = listOf(
|
||||
CheckmarkList.Interval(day(40), day(40), day(34)),
|
||||
CheckmarkList.Interval(day(25), day(25), day(19)),
|
||||
CheckmarkList.Interval(day(18), day(16), day(12)),
|
||||
CheckmarkList.Interval(day(11), day(8), day(5)))
|
||||
CheckmarkList.snapIntervalsTogether(original, dateCalculator)
|
||||
assertEquals(expected, original)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildCheckmarksFromIntervals() {
|
||||
val checks = listOf(Checkmark(day(10), CHECKED_MANUAL),
|
||||
Checkmark(day(5), CHECKED_MANUAL),
|
||||
Checkmark(day(2), CHECKED_MANUAL),
|
||||
Checkmark(day(1), CHECKED_MANUAL))
|
||||
val intervals = listOf(CheckmarkList.Interval(day(10), day(8), day(8)),
|
||||
CheckmarkList.Interval(day(6), day(5), day(4)),
|
||||
CheckmarkList.Interval(day(2), day(2), day(1)))
|
||||
val expected = listOf(Checkmark(day(1), CHECKED_MANUAL),
|
||||
Checkmark(day(2), CHECKED_MANUAL),
|
||||
Checkmark(day(3), UNCHECKED),
|
||||
Checkmark(day(4), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(5), CHECKED_MANUAL),
|
||||
Checkmark(day(6), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(7), UNCHECKED),
|
||||
Checkmark(day(8), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(9), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(10), CHECKED_MANUAL))
|
||||
val actual = CheckmarkList.buildCheckmarksFromIntervals(checks,
|
||||
intervals,
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildCheckmarksFromIntervals2() {
|
||||
val reps = listOf(Checkmark(day(0), CHECKED_MANUAL))
|
||||
val intervals = listOf(CheckmarkList.Interval(day(5), day(0), day(0)))
|
||||
val expected = listOf(Checkmark(day(0), CHECKED_MANUAL),
|
||||
Checkmark(day(1), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(2), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(3), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(4), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(5), CHECKED_AUTOMATIC))
|
||||
val actual = CheckmarkList.buildCheckmarksFromIntervals(reps,
|
||||
intervals,
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeAutomaticCheckmarks() {
|
||||
val checks = listOf(Checkmark(day(10), CHECKED_MANUAL),
|
||||
Checkmark(day(5), CHECKED_MANUAL),
|
||||
Checkmark(day(2), CHECKED_MANUAL),
|
||||
Checkmark(day(1), CHECKED_MANUAL))
|
||||
val expected = listOf(Checkmark(day(-1), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(0), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(1), CHECKED_MANUAL),
|
||||
Checkmark(day(2), CHECKED_MANUAL),
|
||||
Checkmark(day(3), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(4), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(5), CHECKED_MANUAL),
|
||||
Checkmark(day(6), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(7), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(8), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(9), CHECKED_AUTOMATIC),
|
||||
Checkmark(day(10), CHECKED_MANUAL))
|
||||
val actual = CheckmarkList.computeAutomaticCheckmarks(checks,
|
||||
Frequency(1, 3),
|
||||
dateCalculator)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetValuesUntil() {
|
||||
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
|
||||
list.setManualCheckmarks(listOf(Checkmark(day(4), CHECKED_MANUAL),
|
||||
Checkmark(day(7), CHECKED_MANUAL)))
|
||||
val expected = listOf(UNCHECKED,
|
||||
UNCHECKED,
|
||||
UNCHECKED,
|
||||
CHECKED_AUTOMATIC,
|
||||
CHECKED_MANUAL,
|
||||
UNCHECKED,
|
||||
CHECKED_AUTOMATIC,
|
||||
CHECKED_MANUAL)
|
||||
assertEquals(expected, list.getValuesUntil(day(0)))
|
||||
|
||||
val expected2 = listOf(CHECKED_AUTOMATIC,
|
||||
CHECKED_MANUAL,
|
||||
UNCHECKED,
|
||||
CHECKED_AUTOMATIC,
|
||||
CHECKED_MANUAL)
|
||||
assertEquals(expected2, list.getValuesUntil(day(3)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetValuesUntil2() {
|
||||
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
|
||||
val expected = listOf<Int>()
|
||||
assertEquals(expected, list.getValuesUntil(day(0)))
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models
|
||||
|
||||
import junit.framework.TestCase.*
|
||||
import org.isoron.platform.time.*
|
||||
import org.isoron.uhabits.*
|
||||
import org.junit.*
|
||||
|
||||
class CheckmarkRepositoryTest : BaseTest() {
|
||||
@Test
|
||||
fun testCRUD() {
|
||||
val habitA = 10
|
||||
var checkmarksA = listOf(Checkmark(LocalDate(2019, 1, 15), 100),
|
||||
Checkmark(LocalDate(2019, 1, 7), 500),
|
||||
Checkmark(LocalDate(2019, 1, 1), 900))
|
||||
|
||||
val habitB = 35
|
||||
val checkmarksB = listOf(Checkmark(LocalDate(2019, 1, 30), 50),
|
||||
Checkmark(LocalDate(2019, 1, 29), 30),
|
||||
Checkmark(LocalDate(2019, 1, 27), 900),
|
||||
Checkmark(LocalDate(2019, 1, 25), 450),
|
||||
Checkmark(LocalDate(2019, 1, 20), 1000))
|
||||
|
||||
val repository = CheckmarkRepository(db, JavaLocalDateCalculator())
|
||||
|
||||
for (c in checkmarksA) repository.insert(habitA, c)
|
||||
for (c in checkmarksB) repository.insert(habitB, c)
|
||||
assertEquals(checkmarksA, repository.findAll(habitA))
|
||||
assertEquals(checkmarksB, repository.findAll(habitB))
|
||||
assertEquals(listOf<Checkmark>(), repository.findAll(999))
|
||||
|
||||
checkmarksA = listOf(Checkmark(LocalDate(2019, 1, 15), 100),
|
||||
Checkmark(LocalDate(2019, 1, 1), 900))
|
||||
repository.delete(habitA, LocalDate(2019, 1, 7))
|
||||
assertEquals(checkmarksA, repository.findAll(habitA))
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.utils
|
||||
|
||||
import junit.framework.TestCase.*
|
||||
import org.junit.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
class JavaDatesTest {
|
||||
val calc = JavaLocalDateCalculator()
|
||||
|
||||
@Test
|
||||
fun plusMinusDays() {
|
||||
val today = LocalDate(2019, 3, 25)
|
||||
assertEquals(calc.minusDays(today, 28), LocalDate(2019, 2, 25))
|
||||
assertEquals(calc.plusDays(today, 7), LocalDate(2019, 4, 1))
|
||||
assertEquals(calc.plusDays(today, 42), LocalDate(2019, 5, 6))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortMonthName() {
|
||||
var fmt = JavaLocalDateFormatter(Locale.US)
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 3, 25)), "Mon")
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 4, 4)), "Thu")
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 5, 12)), "Sun")
|
||||
|
||||
fmt = JavaLocalDateFormatter(Locale.JAPAN)
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 3, 25)), "月")
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 4, 4)), "木")
|
||||
assertEquals(fmt.shortWeekdayName(LocalDate(2019, 5, 12)), "日")
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
class ShowHabitController : UITableViewController {
|
||||
|
||||
let theme: Theme
|
||||
let color: Color
|
||||
var cells = [UITableViewCell]()
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
init(theme: Theme, color: Color) {
|
||||
self.theme = theme
|
||||
self.color = color
|
||||
super.init(style: .grouped)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
self.title = "Exercise"
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit,
|
||||
target: self,
|
||||
action: #selector(self.onEditHabitClicked))
|
||||
cells.append(buildHistoryChartCell())
|
||||
}
|
||||
|
||||
func buildHistoryChartCell() -> UITableViewCell {
|
||||
let component = CalendarChart(today: LocalDate(year: 2019, month: 3, day: 15),
|
||||
color: color,
|
||||
theme: theme,
|
||||
dateCalculator: IosLocalDateCalculator(),
|
||||
dateFormatter: IosLocalDateFormatter())
|
||||
let cell = UITableViewCell()
|
||||
let view = ComponentView(frame: cell.frame, component: component)
|
||||
var series = [KotlinDouble]()
|
||||
for _ in 1...365 {
|
||||
series.append(KotlinDouble(value: Double.random(in: 0...1)))
|
||||
}
|
||||
component.series = series
|
||||
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
cell.contentView.addSubview(view)
|
||||
return cell
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
self.navigationController?.navigationBar.barStyle = .blackOpaque
|
||||
self.navigationController?.navigationBar.barTintColor = color.uicolor
|
||||
self.navigationController?.navigationBar.tintColor = .white
|
||||
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return cells.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
return cells[indexPath.section]
|
||||
}
|
||||
|
||||
@objc func onEditHabitClicked() {
|
||||
self.navigationController?.pushViewController(EditHabitController(), animated: true)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
return 200
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
create table Habits ( id integer primary key autoincrement, archived integer, color integer, description text, freq_den integer, freq_num integer, highlight integer, name text, position integer, reminder_hour integer, reminder_min integer )
|
||||
create table Checkmarks ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer, value integer )
|
||||
create table Repetitions ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer )
|
||||
create table Streak ( id integer primary key autoincrement, end integer, habit integer references habits(id), length integer, start integer )
|
||||
create table Score ( id integer primary key autoincrement, habit integer references habits(id), score integer, timestamp integer )
|
@ -1,3 +0,0 @@
|
||||
delete from Score
|
||||
delete from Streak
|
||||
delete from Checkmarks
|
@ -1 +0,0 @@
|
||||
alter table Habits add column reminder_days integer not null default 127
|
@ -1,3 +0,0 @@
|
||||
delete from Score
|
||||
delete from Streak
|
||||
delete from Checkmarks
|
@ -1,4 +0,0 @@
|
||||
create index idx_score_habit_timestamp on Score(habit, timestamp)
|
||||
create index idx_checkmark_habit_timestamp on Checkmarks(habit, timestamp)
|
||||
create index idx_repetitions_habit_timestamp on Repetitions(habit, timestamp)
|
||||
create index idx_streak_habit_end on Streak(habit, end)
|
@ -1,14 +0,0 @@
|
||||
update habits set color=0 where color=-2937041
|
||||
update habits set color=1 where color=-1684967
|
||||
update habits set color=2 where color=-415707
|
||||
update habits set color=3 where color=-5262293
|
||||
update habits set color=4 where color=-13070788
|
||||
update habits set color=5 where color=-16742021
|
||||
update habits set color=6 where color=-16732991
|
||||
update habits set color=7 where color=-16540699
|
||||
update habits set color=8 where color=-10603087
|
||||
update habits set color=9 where color=-7461718
|
||||
update habits set color=10 where color=-2614432
|
||||
update habits set color=11 where color=-13619152
|
||||
update habits set color=12 where color=-5592406
|
||||
update habits set color=0 where color<0 or color>12
|
@ -1,3 +0,0 @@
|
||||
delete from Score
|
||||
delete from Streak
|
||||
delete from Checkmarks
|
@ -1,2 +0,0 @@
|
||||
alter table Habits add column type integer not null default 0
|
||||
alter table Repetitions add column value integer not null default 2
|
@ -1,5 +0,0 @@
|
||||
drop table Score
|
||||
create table Score ( id integer primary key autoincrement, habit integer references habits(id), score real, timestamp integer)
|
||||
create index idx_score_habit_timestamp on Score(habit, timestamp)
|
||||
delete from streak
|
||||
delete from checkmarks
|
@ -1,3 +0,0 @@
|
||||
alter table Habits add column target_type integer not null default 0
|
||||
alter table Habits add column target_value real not null default 0
|
||||
alter table Habits add column unit text not null default ""
|
@ -1 +0,0 @@
|
||||
create table Events ( id integer primary key autoincrement, timestamp integer, message text, server_id integer )
|
@ -1,3 +0,0 @@
|
||||
drop table checkmarks
|
||||
drop table streak
|
||||
drop table score
|
@ -1,12 +0,0 @@
|
||||
update habits set color=19 where color=12
|
||||
update habits set color=17 where color=11
|
||||
update habits set color=15 where color=10
|
||||
update habits set color=14 where color=9
|
||||
update habits set color=13 where color=8
|
||||
update habits set color=10 where color=7
|
||||
update habits set color=9 where color=6
|
||||
update habits set color=8 where color=5
|
||||
update habits set color=7 where color=4
|
||||
update habits set color=5 where color=3
|
||||
update habits set color=4 where color=2
|
||||
update habits set color=0 where color<0 or color>19
|
@ -1,11 +0,0 @@
|
||||
delete from repetitions where habit not in (select id from habits)
|
||||
delete from repetitions where timestamp is null
|
||||
delete from repetitions where habit is null
|
||||
delete from repetitions where rowid not in ( select min(rowid) from repetitions group by habit, timestamp )
|
||||
alter table Repetitions rename to RepetitionsBak
|
||||
create table Repetitions ( id integer primary key autoincrement, habit integer not null references habits(id), timestamp integer not null, value integer not null)
|
||||
drop index if exists idx_repetitions_habit_timestamp
|
||||
create unique index idx_repetitions_habit_timestamp on Repetitions( habit, timestamp)
|
||||
insert into Repetitions select * from RepetitionsBak
|
||||
drop table RepetitionsBak
|
||||
pragma foreign_keys=ON
|