Reformat all Kotlin files with ktlint; add check to build script

This commit is contained in:
2020-12-30 15:15:12 -06:00
parent 9a5263e508
commit 9087025418
179 changed files with 3106 additions and 2420 deletions

View File

@@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.core
import javax.inject.*
import javax.inject.Scope
@Scope
annotation class AppScope
annotation class AppScope

View File

@@ -18,14 +18,15 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
data class ArchiveHabitsCommand(
val habitList: HabitList,
val selected: List<Habit>,
val habitList: HabitList,
val selected: List<Habit>,
) : Command {
override fun run() {
for (h in selected) h.isArchived = true
habitList.update(selected)
}
}
}

View File

@@ -18,15 +18,17 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor
data class ChangeHabitColorCommand(
val habitList: HabitList,
val selected: List<Habit>,
val newColor: PaletteColor,
val habitList: HabitList,
val selected: List<Habit>,
val newColor: PaletteColor,
) : Command {
override fun run() {
for (h in selected) h.color = newColor
habitList.update(selected)
}
}
}

View File

@@ -20,4 +20,4 @@ package org.isoron.uhabits.core.commands
interface Command {
fun run()
}
}

View File

@@ -18,27 +18,30 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.tasks.*
import java.util.*
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.tasks.Task
import org.isoron.uhabits.core.tasks.TaskRunner
import java.util.LinkedList
import javax.inject.Inject
@AppScope
open class CommandRunner
@Inject constructor(
private val taskRunner: TaskRunner,
private val taskRunner: TaskRunner,
) {
private val listeners: LinkedList<Listener> = LinkedList()
open fun run(command: Command) {
taskRunner.execute(object : Task {
override fun doInBackground() {
command.run()
taskRunner.execute(
object : Task {
override fun doInBackground() {
command.run()
}
override fun onPostExecute() {
notifyListeners(command)
}
}
override fun onPostExecute() {
notifyListeners(command)
}
})
)
}
fun addListener(l: Listener) {
@@ -56,4 +59,4 @@ open class CommandRunner
interface Listener {
fun onCommandFinished(command: Command)
}
}
}

View File

@@ -18,16 +18,18 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.ModelFactory
data class CreateHabitCommand(
val modelFactory: ModelFactory,
val habitList: HabitList,
val model: Habit,
val modelFactory: ModelFactory,
val habitList: HabitList,
val model: Habit,
) : Command {
override fun run() {
val habit = modelFactory.buildHabit()
habit.copyFrom(model)
habitList.add(habit)
}
}
}

View File

@@ -18,13 +18,16 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Timestamp
data class CreateRepetitionCommand(
val habitList: HabitList,
val habit: Habit,
val timestamp: Timestamp,
val value: Int,
val habitList: HabitList,
val habit: Habit,
val timestamp: Timestamp,
val value: Int,
) : Command {
override fun run() {
val entries = habit.originalEntries
@@ -32,4 +35,4 @@ data class CreateRepetitionCommand(
habit.recompute()
habitList.resort()
}
}
}

View File

@@ -18,13 +18,14 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
data class DeleteHabitsCommand(
val habitList: HabitList,
val selected: List<Habit>,
val habitList: HabitList,
val selected: List<Habit>,
) : Command {
override fun run() {
for (h in selected) habitList.remove(h)
}
}
}

View File

@@ -18,12 +18,14 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitNotFoundException
data class EditHabitCommand(
val habitList: HabitList,
val habitId: Long,
val modified: Habit,
val habitList: HabitList,
val habitId: Long,
val modified: Habit,
) : Command {
override fun run() {
val habit = habitList.getById(habitId) ?: throw HabitNotFoundException()
@@ -32,4 +34,4 @@ data class EditHabitCommand(
habit.observable.notifyListeners()
habit.recompute()
}
}
}

View File

@@ -18,14 +18,15 @@
*/
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
data class UnarchiveHabitsCommand(
val habitList: HabitList,
val selected: List<Habit>,
val habitList: HabitList,
val selected: List<Habit>,
) : Command {
override fun run() {
for (h in selected) h.isArchived = false
habitList.update(selected)
}
}
}

View File

@@ -19,4 +19,4 @@
package org.isoron.uhabits.core.database
@Retention(AnnotationRetention.RUNTIME)
annotation class Column(val name: String = "")
annotation class Column(val name: String = "")

View File

@@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.core.database
import java.io.*
import java.io.Closeable
interface Cursor : Closeable {
@@ -59,4 +59,4 @@ interface Cursor : Closeable {
* column has index zero.
*/
fun getString(index: Int): String?
}
}

View File

@@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.core.database
import java.io.*
import java.io.File
interface Database {
@@ -32,10 +32,10 @@ interface Database {
}
fun update(
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String,
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String,
): Int
fun insert(tableName: String, values: Map<String, Any?>): Long?
@@ -59,4 +59,4 @@ interface Database {
interface ProcessCallback {
fun process(cursor: Cursor)
}
}
}

View File

@@ -18,8 +18,8 @@
*/
package org.isoron.uhabits.core.database
import java.io.*
import java.io.File
interface DatabaseOpener {
fun open(file: File): Database?
}
}

View File

@@ -20,7 +20,8 @@
*/
package org.isoron.uhabits.core.database
import java.sql.*
import java.sql.ResultSet
import java.sql.SQLException
class JdbcCursor(private val resultSet: ResultSet) : Cursor {
override fun close() {
@@ -74,4 +75,4 @@ class JdbcCursor(private val resultSet: ResultSet) : Cursor {
throw RuntimeException(e)
}
}
}
}

View File

@@ -18,11 +18,13 @@
*/
package org.isoron.uhabits.core.database
import org.apache.commons.lang3.*
import java.io.*
import java.lang.IllegalArgumentException
import java.sql.*
import java.util.*
import org.apache.commons.lang3.StringUtils
import java.io.File
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLException
import java.sql.Types
import java.util.ArrayList
class JdbcDatabase(private val connection: Connection) : Database {
private var transactionSuccessful = false
@@ -36,10 +38,10 @@ class JdbcDatabase(private val connection: Connection) : Database {
}
override fun update(
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String,
tableName: String,
values: Map<String, Any?>,
where: String,
vararg params: String,
): Int {
return try {
val fields = ArrayList<String?>()
@@ -49,8 +51,12 @@ class JdbcDatabase(private val connection: Connection) : Database {
valuesStr.add(value.toString())
}
valuesStr.addAll(listOf(*params))
val query = String.format("update %s set %s where %s", tableName,
StringUtils.join(fields, ", "), where)
val query = String.format(
"update %s set %s where %s",
tableName,
StringUtils.join(fields, ", "),
where
)
val st = buildStatement(query, valuesStr.toTypedArray())
st.executeUpdate()
} catch (e: SQLException) {
@@ -68,9 +74,12 @@ class JdbcDatabase(private val connection: Connection) : Database {
params.add(value)
questionMarks.add("?")
}
val query = String.format("insert into %s(%s) values(%s)", tableName,
StringUtils.join(fields, ", "),
StringUtils.join(questionMarks, ", "))
val query = String.format(
"insert into %s(%s) values(%s)",
tableName,
StringUtils.join(fields, ", "),
StringUtils.join(questionMarks, ", ")
)
val st = buildStatement(query, params.toTypedArray())
st.execute()
var id: Long? = null
@@ -154,4 +163,4 @@ class JdbcDatabase(private val connection: Connection) : Database {
override val file: File?
get() = null
}
}

View File

@@ -18,11 +18,13 @@
*/
package org.isoron.uhabits.core.database
import java.io.*
import java.util.*
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.util.Locale
class MigrationHelper(
private val db: Database,
private val db: Database,
) {
fun migrateTo(newVersion: Int) {
try {
@@ -46,4 +48,4 @@ class MigrationHelper(
if (file.exists()) return FileInputStream(file)
throw RuntimeException("resource not found: $fname")
}
}
}

View File

@@ -18,14 +18,17 @@
*/
package org.isoron.uhabits.core.database
import org.apache.commons.lang3.*
import org.apache.commons.lang3.tuple.*
import java.lang.reflect.*
import java.util.*
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.tuple.ImmutablePair
import org.apache.commons.lang3.tuple.Pair
import java.lang.reflect.Field
import java.util.ArrayList
import java.util.HashMap
import java.util.LinkedList
class Repository<T>(
private val klass: Class<T>,
private val db: Database,
private val klass: Class<T>,
private val db: Database,
) {
/**
* Returns the record that has the id provided. If no record is found, returns null.
@@ -249,4 +252,4 @@ class Repository<T>(
if (t == null) throw RuntimeException("Table annotation not found")
return t
}
}
}

View File

@@ -15,11 +15,12 @@
*/
package org.isoron.uhabits.core.database
import java.io.*
import java.util.*
import java.io.BufferedInputStream
import java.io.InputStream
import java.util.ArrayList
internal class Tokenizer(
private val mStream: InputStream,
private val mStream: InputStream,
) {
private var mIsNext = false
private var mCurrent = 0
@@ -127,4 +128,4 @@ object SQLParser {
private fun isWhitespace(c: Char): Boolean {
return c == '\r' || c == '\n' || c == '\t' || c == ' '
}
}
}

View File

@@ -20,4 +20,4 @@ package org.isoron.uhabits.core.database
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Table(val name: String, val id: String = "id")
annotation class Table(val name: String, val id: String = "id")

View File

@@ -18,4 +18,4 @@
*/
package org.isoron.uhabits.core.database
class UnsupportedDatabaseVersionException : RuntimeException()
class UnsupportedDatabaseVersionException : RuntimeException()

View File

@@ -18,9 +18,9 @@
*/
package org.isoron.uhabits.core.io
import org.isoron.uhabits.core.models.*
import java.io.*
import javax.inject.*
import org.isoron.uhabits.core.models.HabitList
import java.io.File
import javax.inject.Inject
/**
* A GenericImporter decides which implementation of AbstractImporter is able to
@@ -28,18 +28,18 @@ import javax.inject.*
*/
class GenericImporter
@Inject constructor(
habits: HabitList,
loopDBImporter: LoopDBImporter,
rewireDBImporter: RewireDBImporter,
tickmateDBImporter: TickmateDBImporter,
habitBullCSVImporter: HabitBullCSVImporter,
habits: HabitList,
loopDBImporter: LoopDBImporter,
rewireDBImporter: RewireDBImporter,
tickmateDBImporter: TickmateDBImporter,
habitBullCSVImporter: HabitBullCSVImporter,
) : AbstractImporter(habits) {
var importers: List<AbstractImporter> = listOf(
loopDBImporter,
rewireDBImporter,
tickmateDBImporter,
habitBullCSVImporter,
loopDBImporter,
rewireDBImporter,
tickmateDBImporter,
habitBullCSVImporter,
)
override fun canHandle(file: File): Boolean {
@@ -58,4 +58,4 @@ class GenericImporter
}
}
}
}
}

View File

@@ -18,20 +18,27 @@
*/
package org.isoron.uhabits.core.io
import com.opencsv.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.utils.*
import java.io.*
import java.util.*
import javax.inject.*
import com.opencsv.CSVReader
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.utils.DateUtils
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.util.HashMap
import javax.inject.Inject
/**
* Class that imports data from HabitBull CSV files.
*/
class HabitBullCSVImporter
@Inject constructor(
habits: HabitList,
private val modelFactory: ModelFactory,
habits: HabitList,
private val modelFactory: ModelFactory,
) : AbstractImporter(habits) {
override fun canHandle(file: File): Boolean {
@@ -68,4 +75,4 @@ class HabitBullCSVImporter
h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL))
}
}
}
}

View File

@@ -19,8 +19,6 @@
package org.isoron.uhabits.core.io
import java.lang.Exception
interface Logging {
fun getLogger(name: String): Logger
}
@@ -38,7 +36,7 @@ class StandardLogging : Logging {
}
}
class StandardLogger(val name: String): Logger {
class StandardLogger(val name: String) : Logger {
override fun info(msg: String) {
println("[$name] $msg")
}
@@ -54,5 +52,4 @@ class StandardLogger(val name: String): Logger {
override fun error(exception: Exception) {
exception.printStackTrace()
}
}
}

View File

@@ -18,24 +18,34 @@
*/
package org.isoron.uhabits.core.io
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.sqlite.records.*
import java.io.*
import javax.inject.*
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.DATABASE_VERSION
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.database.DatabaseOpener
import org.isoron.uhabits.core.database.MigrationHelper
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
import java.io.File
import javax.inject.Inject
/**
* Class that imports data from database files exported by Loop Habit Tracker.
*/
class LoopDBImporter
@Inject constructor(
@AppScope val habitList: HabitList,
@AppScope val modelFactory: ModelFactory,
@AppScope val opener: DatabaseOpener,
@AppScope val runner: CommandRunner,
@AppScope logging: Logging,
@AppScope val habitList: HabitList,
@AppScope val modelFactory: ModelFactory,
@AppScope val opener: DatabaseOpener,
@AppScope val runner: CommandRunner,
@AppScope logging: Logging,
) : AbstractImporter(habitList) {
private val logger = logging.getLogger("LoopDBImporter")
@@ -99,5 +109,4 @@ class LoopDBImporter
db.close()
}
}
}

View File

@@ -19,8 +19,8 @@
package org.isoron.uhabits.core.models
data class Entry(
val timestamp: Timestamp,
val value: Int,
val timestamp: Timestamp,
val value: Int,
) {
companion object {
/**
@@ -65,6 +65,5 @@ data class Entry(
else -> NO
}
}
}
}
}

View File

@@ -23,12 +23,12 @@ import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.utils.*
import java.util.*
import javax.annotation.concurrent.*
import kotlin.collections.HashMap
import org.isoron.uhabits.core.utils.DateUtils
import java.util.ArrayList
import java.util.Calendar
import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.set
import kotlin.math.*
import kotlin.math.min
@ThreadSafe
open class EntryList {
@@ -90,10 +90,10 @@ open class EntryList {
*/
@Synchronized
open fun groupBy(
original: List<Entry>,
field: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean,
original: List<Entry>,
field: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean,
): List<Entry> {
val truncated = original.map {
Entry(it.timestamp.truncate(field, firstWeekday), it.value)
@@ -126,9 +126,9 @@ open class EntryList {
*/
@Synchronized
open fun recomputeFrom(
originalEntries: EntryList,
frequency: Frequency,
isNumerical: Boolean,
originalEntries: EntryList,
frequency: Frequency,
isNumerical: Boolean,
) {
clear()
val original = originalEntries.getKnown()
@@ -168,9 +168,11 @@ open class EntryList {
val map = hashMapOf<Timestamp, Array<Int>>()
for ((originalTimestamp, value) in entries) {
val weekday = originalTimestamp.weekday
val truncatedTimestamp = Timestamp(originalTimestamp.toCalendar().apply {
set(Calendar.DAY_OF_MONTH, 1)
}.timeInMillis)
val truncatedTimestamp = Timestamp(
originalTimestamp.toCalendar().apply {
set(Calendar.DAY_OF_MONTH, 1)
}.timeInMillis
)
var list = map[truncatedTimestamp]
if (list == null) {
@@ -224,9 +226,9 @@ open class EntryList {
@Synchronized
open fun getThisWeekValue(firstWeekday: Int, isNumerical: Boolean): Int {
return getThisIntervalValue(
truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday,
isNumerical = isNumerical
truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday,
isNumerical = isNumerical
)
}
@@ -234,9 +236,9 @@ open class EntryList {
@Synchronized
open fun getThisMonthValue(isNumerical: Boolean): Int {
return getThisIntervalValue(
truncateField = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
truncateField = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
)
}
@@ -244,9 +246,9 @@ open class EntryList {
@Synchronized
open fun getThisQuarterValue(isNumerical: Boolean): Int {
return getThisIntervalValue(
truncateField = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
truncateField = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
)
}
@@ -254,16 +256,16 @@ open class EntryList {
@Synchronized
open fun getThisYearValue(isNumerical: Boolean): Int {
return getThisIntervalValue(
truncateField = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
truncateField = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = isNumerical
)
}
private fun getThisIntervalValue(
truncateField: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean,
truncateField: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean,
): Int {
val groups: List<Entry> = groupBy(getKnown(), truncateField, firstWeekday, isNumerical)
return if (groups.isEmpty()) 0 else groups[0].value
@@ -271,7 +273,7 @@ open class EntryList {
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
val length: Int
get() = begin.daysUntil(end) + 1;
get() = begin.daysUntil(end) + 1
}
/**
@@ -284,8 +286,8 @@ open class EntryList {
*/
companion object {
fun buildEntriesFromInterval(
original: List<Entry>,
intervals: List<Interval>,
original: List<Entry>,
intervals: List<Interval>,
): List<Entry> {
val result = arrayListOf<Entry>()
if (original.isEmpty()) return result
@@ -346,16 +348,18 @@ open class EntryList {
val gapCenterToEnd = curr.center.daysUntil(curr.end)
if (gapNextToCurrent >= 0) {
val shift = min(gapCenterToEnd, gapNextToCurrent + 1)
intervals[i] = Interval(curr.begin.minus(shift),
curr.center,
curr.end.minus(shift))
intervals[i] = Interval(
curr.begin.minus(shift),
curr.center,
curr.end.minus(shift)
)
}
}
}
fun buildIntervals(
freq: Frequency,
entries: List<Entry>,
freq: Frequency,
entries: List<Entry>,
): ArrayList<Interval> {
val filtered = entries.filter { it.value == YES_MANUAL }
val num = freq.numerator
@@ -372,4 +376,4 @@ open class EntryList {
return intervals
}
}
}
}

View File

@@ -19,8 +19,8 @@
package org.isoron.uhabits.core.models
data class Frequency(
var numerator: Int,
var denominator: Int,
var numerator: Int,
var denominator: Int,
) {
init {
if (numerator == denominator) {
@@ -46,4 +46,4 @@ data class Frequency(
@JvmField
val WEEKLY = Frequency(1, 7)
}
}
}

View File

@@ -18,31 +18,31 @@
*/
package org.isoron.uhabits.core.models
import org.isoron.uhabits.core.utils.*
import java.util.*
import org.isoron.uhabits.core.utils.DateUtils
import java.util.UUID
data class Habit(
var color: PaletteColor = PaletteColor(8),
var description: String = "",
var frequency: Frequency = Frequency.DAILY,
var id: Long? = null,
var isArchived: Boolean = false,
var name: String = "",
var position: Int = 0,
var question: String = "",
var reminder: Reminder? = null,
var targetType: Int = AT_LEAST,
var targetValue: Double = 0.0,
var type: Int = YES_NO_HABIT,
var unit: String = "",
var uuid: String? = null,
val computedEntries: EntryList,
val originalEntries: EntryList,
val scores: ScoreList,
val streaks: StreakList,
var color: PaletteColor = PaletteColor(8),
var description: String = "",
var frequency: Frequency = Frequency.DAILY,
var id: Long? = null,
var isArchived: Boolean = false,
var name: String = "",
var position: Int = 0,
var question: String = "",
var reminder: Reminder? = null,
var targetType: Int = AT_LEAST,
var targetValue: Double = 0.0,
var type: Int = YES_NO_HABIT,
var unit: String = "",
var uuid: String? = null,
val computedEntries: EntryList,
val originalEntries: EntryList,
val scores: ScoreList,
val streaks: StreakList,
) {
init {
if(uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "");
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
}
var observable = ModelObservable()
@@ -71,9 +71,9 @@ data class Habit(
fun recompute() {
computedEntries.recomputeFrom(
originalEntries = originalEntries,
frequency = frequency,
isNumerical = isNumerical,
originalEntries = originalEntries,
frequency = frequency,
isNumerical = isNumerical,
)
val to = DateUtils.getTodayWithOffset().plus(30)
@@ -82,18 +82,18 @@ data class Habit(
if (from.isNewerThan(to)) from = to
scores.recompute(
frequency = frequency,
isNumerical = isNumerical,
targetValue = targetValue,
computedEntries = computedEntries,
from = from,
to = to,
frequency = frequency,
isNumerical = isNumerical,
targetValue = targetValue,
computedEntries = computedEntries,
from = from,
to = to,
)
streaks.recompute(
computedEntries,
from,
to,
computedEntries,
from,
to,
)
}
@@ -160,4 +160,4 @@ data class Habit(
const val NUMBER_HABIT = 1
const val YES_NO_HABIT = 0
}
}
}

View File

@@ -19,9 +19,9 @@
package org.isoron.uhabits.core.models
data class HabitMatcher(
val isArchivedAllowed: Boolean = false,
val isReminderRequired: Boolean = false,
val isCompletedAllowed: Boolean = true,
val isArchivedAllowed: Boolean = false,
val isReminderRequired: Boolean = false,
val isCompletedAllowed: Boolean = true,
) {
fun matches(habit: Habit): Boolean {
if (!isArchivedAllowed && habit.isArchived) return false
@@ -33,8 +33,8 @@ data class HabitMatcher(
companion object {
@JvmField
val WITH_ALARM = HabitMatcherBuilder()
.setArchivedAllowed(true)
.setReminderRequired(true)
.build()
.setArchivedAllowed(true)
.setReminderRequired(true)
.build()
}
}
}

View File

@@ -25,9 +25,9 @@ class HabitMatcherBuilder {
fun build(): HabitMatcher {
return HabitMatcher(
isArchivedAllowed = archivedAllowed,
isReminderRequired = reminderRequired,
isCompletedAllowed = completedAllowed,
isArchivedAllowed = archivedAllowed,
isReminderRequired = reminderRequired,
isCompletedAllowed = completedAllowed,
)
}
@@ -45,4 +45,4 @@ class HabitMatcherBuilder {
this.reminderRequired = reminderRequired
return this
}
}
}

View File

@@ -18,4 +18,4 @@
*/
package org.isoron.uhabits.core.models
class HabitNotFoundException : RuntimeException()
class HabitNotFoundException : RuntimeException()

View File

@@ -18,8 +18,9 @@
*/
package org.isoron.uhabits.core.models
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.sqlite.records.*
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
/**
* Interface implemented by factories that provide concrete implementations of
@@ -31,10 +32,10 @@ interface ModelFactory {
val scores = buildScoreList()
val streaks = buildStreakList()
val habit = Habit(
scores = scores,
streaks = streaks,
originalEntries = buildOriginalEntries(),
computedEntries = buildComputedEntries(),
scores = scores,
streaks = streaks,
originalEntries = buildOriginalEntries(),
computedEntries = buildComputedEntries(),
)
return habit
}
@@ -45,4 +46,4 @@ interface ModelFactory {
fun buildStreakList(): StreakList
fun buildHabitListRepository(): Repository<HabitRecord>
fun buildRepetitionListRepository(): Repository<EntryRecord>
}
}

View File

@@ -22,30 +22,30 @@ package org.isoron.uhabits.core.models
data class PaletteColor(val paletteIndex: Int) {
fun toCsvColor(): String {
return arrayOf(
"#D32F2F", // 0 red
"#E64A19", // 1 deep orange
"#F57C00", // 2 orange
"#FF8F00", // 3 amber
"#F9A825", // 4 yellow
"#AFB42B", // 5 lime
"#7CB342", // 6 light green
"#388E3C", // 7 green
"#00897B", // 8 teal
"#00ACC1", // 9 cyan
"#039BE5", // 10 light blue
"#1976D2", // 11 blue
"#303F9F", // 12 indigo
"#5E35B1", // 13 deep purple
"#8E24AA", // 14 purple
"#D81B60", // 15 pink
"#5D4037", // 16 brown
"#303030", // 17 dark grey
"#757575", // 18 grey
"#aaaaaa" // 19 light grey
"#D32F2F", // 0 red
"#E64A19", // 1 deep orange
"#F57C00", // 2 orange
"#FF8F00", // 3 amber
"#F9A825", // 4 yellow
"#AFB42B", // 5 lime
"#7CB342", // 6 light green
"#388E3C", // 7 green
"#00897B", // 8 teal
"#00ACC1", // 9 cyan
"#039BE5", // 10 light blue
"#1976D2", // 11 blue
"#303F9F", // 12 indigo
"#5E35B1", // 13 deep purple
"#8E24AA", // 14 purple
"#D81B60", // 15 pink
"#5D4037", // 16 brown
"#303030", // 17 dark grey
"#757575", // 18 grey
"#aaaaaa" // 19 light grey
)[paletteIndex]
}
fun compareTo(other: PaletteColor): Int {
return paletteIndex.compareTo(other.paletteIndex)
}
}
}

View File

@@ -18,13 +18,13 @@
*/
package org.isoron.uhabits.core.models
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.core.utils.DateUtils
data class Reminder(
val hour: Int,
val minute: Int,
val days: WeekdayList,
val hour: Int,
val minute: Int,
val days: WeekdayList,
) {
val timeInMillis: Long
get() = DateUtils.getUpcomingTimeInMillis(hour, minute)
}
}

View File

@@ -18,11 +18,11 @@
*/
package org.isoron.uhabits.core.models
import kotlin.math.*
import kotlin.math.sqrt
data class Score(
val timestamp: Timestamp,
val value: Double,
val timestamp: Timestamp,
val value: Double,
) {
companion object {
@@ -36,9 +36,9 @@ data class Score(
*/
@JvmStatic
fun compute(
frequency: Double,
previousScore: Double,
checkmarkValue: Double,
frequency: Double,
previousScore: Double,
checkmarkValue: Double,
): Double {
val multiplier = Math.pow(0.5, sqrt(frequency) / 13.0)
var score = previousScore * multiplier
@@ -46,4 +46,4 @@ data class Score(
return score
}
}
}
}

View File

@@ -19,9 +19,10 @@
package org.isoron.uhabits.core.models
import org.isoron.uhabits.core.models.Score.Companion.compute
import java.util.*
import javax.annotation.concurrent.*
import kotlin.math.*
import java.util.ArrayList
import java.util.HashMap
import javax.annotation.concurrent.ThreadSafe
import kotlin.math.min
@ThreadSafe
class ScoreList {
@@ -46,8 +47,8 @@ class ScoreList {
*/
@Synchronized
fun getByInterval(
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
): List<Score> {
val result: MutableList<Score> = ArrayList()
if (fromTimestamp.isNewerThan(toTimestamp)) return result
@@ -64,12 +65,12 @@ class ScoreList {
*/
@Synchronized
fun recompute(
frequency: Frequency,
isNumerical: Boolean,
targetValue: Double,
computedEntries: EntryList,
from: Timestamp,
to: Timestamp,
frequency: Frequency,
isNumerical: Boolean,
targetValue: Double,
computedEntries: EntryList,
from: Timestamp,
to: Timestamp,
) {
map.clear()
if (computedEntries.getKnown().isEmpty()) return
@@ -116,4 +117,4 @@ class ScoreList {
map[timestamp] = Score(timestamp, previousValue)
}
}
}
}

View File

@@ -18,11 +18,11 @@
*/
package org.isoron.uhabits.core.models
import java.lang.Long.*
import java.lang.Long.signum
data class Streak(
val start: Timestamp,
val end: Timestamp,
val start: Timestamp,
val end: Timestamp,
) {
fun compareLonger(other: Streak): Int {
return if (length != other.length) signum(length - other.length.toLong())
@@ -35,4 +35,4 @@ data class Streak(
val length: Int
get() = start.daysUntil(end) + 1
}
}

View File

@@ -18,8 +18,8 @@
*/
package org.isoron.uhabits.core.models
import javax.annotation.concurrent.*
import kotlin.math.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.math.min
@ThreadSafe
class StreakList {
@@ -35,16 +35,16 @@ class StreakList {
@Synchronized
fun recompute(
computedEntries: EntryList,
from: Timestamp,
to: Timestamp,
computedEntries: EntryList,
from: Timestamp,
to: Timestamp,
) {
list.clear()
val timestamps = computedEntries
.getByInterval(from, to)
.filter { it.value > 0 }
.map { it.timestamp }
.toTypedArray()
.getByInterval(from, to)
.filter { it.value > 0 }
.map { it.timestamp }
.toTypedArray()
if (timestamps.isEmpty()) return
@@ -62,4 +62,4 @@ class StreakList {
}
list.add(Streak(begin, end))
}
}
}

View File

@@ -18,7 +18,10 @@
*/
package org.isoron.uhabits.core.models.memory
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.EntryList
import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.ScoreList
import org.isoron.uhabits.core.models.StreakList
class MemoryModelFactory : ModelFactory {
override fun buildComputedEntries() = EntryList()
@@ -28,4 +31,4 @@ class MemoryModelFactory : ModelFactory {
override fun buildStreakList() = StreakList()
override fun buildHabitListRepository() = throw NotImplementedError()
override fun buildRepetitionListRepository() = throw NotImplementedError()
}
}

View File

@@ -20,17 +20,22 @@
*/
package org.isoron.uhabits.core.models.sqlite
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.sqlite.records.*
import javax.inject.*
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.EntryList
import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.ScoreList
import org.isoron.uhabits.core.models.StreakList
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
import javax.inject.Inject
/**
* Factory that provides models backed by an SQLite database.
*/
class SQLModelFactory
@Inject constructor(
val database: Database,
val database: Database,
) : ModelFactory {
override fun buildOriginalEntries() = SQLiteEntryList(database)
override fun buildComputedEntries() = EntryList()
@@ -39,8 +44,8 @@ class SQLModelFactory
override fun buildStreakList() = StreakList()
override fun buildHabitListRepository() =
Repository(HabitRecord::class.java, database)
Repository(HabitRecord::class.java, database)
override fun buildRepetitionListRepository() =
Repository(EntryRecord::class.java, database)
}
Repository(EntryRecord::class.java, database)
}

View File

@@ -19,10 +19,14 @@
package org.isoron.uhabits.core.models.sqlite
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.sqlite.records.*
import org.isoron.uhabits.core.utils.*
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.EntryList
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.utils.DateUtils
class SQLiteEntryList(database: Database) : EntryList() {
val repository = Repository(EntryRecord::class.java, database)
@@ -32,8 +36,10 @@ class SQLiteEntryList(database: Database) : EntryList() {
private fun loadRecords() {
if (isLoaded) return
val habitId = habitId ?: throw IllegalStateException("habitId must be set")
val records = repository.findAll("where habit = ? order by timestamp",
habitId.toString())
val records = repository.findAll(
"where habit = ? order by timestamp",
habitId.toString()
)
for (rec in records) super.add(rec.toEntry())
isLoaded = true
}
@@ -53,9 +59,11 @@ class SQLiteEntryList(database: Database) : EntryList() {
val habitId = habitId ?: throw IllegalStateException("habitId must be set")
// Remove existing rows
repository.execSQL("delete from repetitions where habit = ? and timestamp = ?",
habitId.toString(),
entry.timestamp.unixTime.toString())
repository.execSQL(
"delete from repetitions where habit = ? and timestamp = ?",
habitId.toString(),
entry.timestamp.unixTime.toString()
)
// Add new row
val record = EntryRecord().apply { copyFrom(entry) }
@@ -72,10 +80,10 @@ class SQLiteEntryList(database: Database) : EntryList() {
}
override fun groupBy(
original: List<Entry>,
field: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean
original: List<Entry>,
field: DateUtils.TruncateField,
firstWeekday: Int,
isNumerical: Boolean
): List<Entry> {
loadRecords()
return super.groupBy(original, field, firstWeekday, isNumerical)
@@ -87,7 +95,9 @@ class SQLiteEntryList(database: Database) : EntryList() {
override fun clear() {
super.clear()
repository.execSQL("delete from repetitions where habit = ?",
habitId.toString())
repository.execSQL(
"delete from repetitions where habit = ?",
habitId.toString()
)
}
}

View File

@@ -57,4 +57,4 @@ interface AbstractSyncServer {
* to insufficient server resources or network problems.
*/
suspend fun getDataVersion(key: String): Long
}
}

View File

@@ -21,14 +21,23 @@
package org.isoron.uhabits.core.sync
import com.google.common.io.*
import kotlinx.coroutines.*
import org.apache.commons.codec.binary.*
import java.io.*
import java.nio.*
import java.util.zip.*
import javax.crypto.*
import javax.crypto.spec.*
import com.google.common.io.ByteStreams
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import org.apache.commons.codec.binary.Base64
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Encryption key which can be used with [File.encryptToString], [String.decryptToFile],
@@ -38,8 +47,8 @@ import javax.crypto.spec.*
* Base64-encoded string, use [EncryptionKey.fromBase64].
*/
class EncryptionKey private constructor(
val base64: String,
val secretKey: SecretKey
val base64: String,
val secretKey: SecretKey
) {
companion object {
@@ -75,10 +84,10 @@ fun ByteArray.encrypt(key: EncryptionKey): ByteArray {
cipher.init(Cipher.ENCRYPT_MODE, key.secretKey)
val encrypted = cipher.doFinal(this)
return ByteBuffer
.allocate(16 + encrypted.size)
.put(cipher.iv)
.put(encrypted)
.array()
.allocate(16 + encrypted.size)
.put(cipher.iv)
.put(encrypted)
.array()
}
/**
@@ -131,4 +140,3 @@ fun File.encryptToString(key: EncryptionKey): String {
fun ByteArray.encodeBase64(): String = Base64.encodeBase64(this).decodeToString()
fun String.decodeBase64(): ByteArray = Base64.decodeBase64(this.toByteArray())

View File

@@ -26,4 +26,4 @@ interface NetworkManager {
fun onNetworkAvailable()
fun onNetworkLost()
}
}
}

View File

@@ -19,10 +19,10 @@
package org.isoron.uhabits.core.sync
open class SyncException: RuntimeException()
open class SyncException : RuntimeException()
class KeyNotFoundException: SyncException()
class KeyNotFoundException : SyncException()
class ServiceUnavailable: SyncException()
class ServiceUnavailable : SyncException()
class EditConflictException: SyncException()
class EditConflictException : SyncException()

View File

@@ -19,24 +19,29 @@
package org.isoron.uhabits.core.sync
import kotlinx.coroutines.*
import org.isoron.uhabits.core.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.io.*
import org.isoron.uhabits.core.preferences.*
import java.io.*
import javax.inject.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.invoke
import kotlinx.coroutines.launch
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.io.Logging
import org.isoron.uhabits.core.io.LoopDBImporter
import org.isoron.uhabits.core.preferences.Preferences
import java.io.File
import javax.inject.Inject
@AppScope
class SyncManager @Inject constructor(
val preferences: Preferences,
val commandRunner: CommandRunner,
val logging: Logging,
val networkManager: NetworkManager,
val server: AbstractSyncServer,
val db: Database,
val dbImporter: LoopDBImporter,
val preferences: Preferences,
val commandRunner: CommandRunner,
val logging: Logging,
val networkManager: NetworkManager,
val server: AbstractSyncServer,
val db: Database,
val dbImporter: LoopDBImporter,
) : Preferences.Listener, CommandRunner.Listener, NetworkManager.Listener {
private var logger = logging.getLogger("SyncManager")

View File

@@ -19,8 +19,8 @@
package org.isoron.uhabits.core.ui.callbacks
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Timestamp
interface OnToggleCheckmarkListener {
fun onToggleEntry(timestamp: Timestamp, value: Int) {}
}
}

View File

@@ -18,19 +18,22 @@
*/
package org.isoron.uhabits.core.ui.screens.habits.show
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.ui.screens.habits.list.*
import kotlin.math.*
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.callbacks.OnToggleCheckmarkListener
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import kotlin.math.roundToInt
class ShowHabitBehavior(
private val habitList: HabitList,
private val commandRunner: CommandRunner,
private val habit: Habit,
private val screen: Screen,
private val preferences: Preferences,
private val habitList: HabitList,
private val commandRunner: CommandRunner,
private val habit: Habit,
private val screen: Screen,
private val preferences: Preferences,
) : OnToggleCheckmarkListener {
fun onScoreCardSpinnerPosition(position: Int) {
@@ -62,33 +65,35 @@ class ShowHabitBehavior(
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
val thousands = (newValue * 1000).roundToInt()
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
thousands,
),
CreateRepetitionCommand(
habitList,
habit,
timestamp,
thousands,
),
)
}
} else {
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
value,
),
CreateRepetitionCommand(
habitList,
habit,
timestamp,
value,
),
)
}
}
interface Screen {
fun showNumberPicker(value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback)
fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback
)
fun updateWidgets()
fun refresh()
fun showHistoryEditorDialog(listener: OnToggleCheckmarkListener)
}
}
}

View File

@@ -18,21 +18,25 @@
*/
package org.isoron.uhabits.core.ui.screens.habits.show
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.callbacks.*
import org.isoron.uhabits.core.utils.*
import java.io.*
import java.util.*
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.tasks.ExportCSVTask
import org.isoron.uhabits.core.tasks.TaskRunner
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.utils.DateUtils
import java.io.File
import java.util.Random
class ShowHabitMenuBehavior(
private val commandRunner: CommandRunner,
private val habit: Habit,
private val habitList: HabitList,
private val screen: Screen,
private val system: System,
private val taskRunner: TaskRunner,
private val commandRunner: CommandRunner,
private val habit: Habit,
private val habitList: HabitList,
private val screen: Screen,
private val system: System,
private val taskRunner: TaskRunner,
) {
fun onEditHabit() {
screen.showEditHabitScreen(habit)
@@ -40,13 +44,15 @@ class ShowHabitMenuBehavior(
fun onExportCSV() {
val outputDir = system.getCSVOutputDir()
taskRunner.execute(ExportCSVTask(habitList, listOf(habit), outputDir) { filename: String? ->
if (filename != null) {
screen.showSendFileScreen(filename)
} else {
screen.showMessage(Message.COULD_NOT_EXPORT)
taskRunner.execute(
ExportCSVTask(habitList, listOf(habit), outputDir) { filename: String? ->
if (filename != null) {
screen.showSendFileScreen(filename)
} else {
screen.showMessage(Message.COULD_NOT_EXPORT)
}
}
})
)
}
fun onDeleteHabit() {
@@ -80,7 +86,8 @@ class ShowHabitMenuBehavior(
fun showMessage(m: Message?)
fun showSendFileScreen(filename: String)
fun showDeleteConfirmationScreen(
callback: OnConfirmedCallback)
callback: OnConfirmedCallback
)
fun close()
fun refresh()
}
@@ -88,4 +95,4 @@ class ShowHabitMenuBehavior(
interface System {
fun getCSVOutputDir(): File
}
}
}

View File

@@ -19,15 +19,16 @@
package org.isoron.uhabits.core.ui.screens.sync
import org.isoron.uhabits.core.io.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.sync.*
import org.isoron.uhabits.core.io.Logging
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.sync.AbstractSyncServer
import org.isoron.uhabits.core.sync.EncryptionKey
class SyncBehavior(
val screen: Screen,
val preferences: Preferences,
val server: AbstractSyncServer,
val logging: Logging,
val screen: Screen,
val preferences: Preferences,
val server: AbstractSyncServer,
val logging: Logging,
) {
val logger = logging.getLogger("SyncBehavior")
@@ -62,4 +63,4 @@ class SyncBehavior(
suspend fun showErrorScreen()
suspend fun showLink(link: String)
}
}
}

View File

@@ -9,7 +9,7 @@ import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.Test
class Version23Test: BaseUnitTest() {
class Version23Test : BaseUnitTest() {
private lateinit var db: Database
@@ -38,14 +38,14 @@ class Version23Test: BaseUnitTest() {
var cursor = db.query("select description from Habits")
val descriptions = mutableListOf<String?>()
while(cursor.moveToNext()){
while (cursor.moveToNext()) {
descriptions.add(cursor.getString(0))
}
migrateTo23()
cursor = db.query("select question from Habits")
for(i in 0 until descriptions.size){
for (i in 0 until descriptions.size) {
cursor.moveToNext()
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(descriptions[i]))
}
@@ -56,8 +56,8 @@ class Version23Test: BaseUnitTest() {
migrateTo23()
val cursor = db.query("select description from Habits")
while(cursor.moveToNext()){
while (cursor.moveToNext()) {
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(""))
}
}
}
}

View File

@@ -19,16 +19,17 @@
package org.isoron.uhabits.core.models
import org.hamcrest.MatcherAssert.*
import org.hamcrest.core.IsEqual.*
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo
import org.isoron.uhabits.core.models.Entry.Companion.NO
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.utils.*
import org.junit.*
import java.util.*
import kotlin.test.*
import org.isoron.uhabits.core.utils.DateUtils
import org.junit.Test
import java.util.Calendar
import java.util.Random
import kotlin.test.assertEquals
class EntryListTest {
@Test
@@ -74,13 +75,13 @@ class EntryListTest {
entries.add(Entry(today.minus(6), YES_MANUAL))
val expected = intArrayOf(
UNKNOWN, // 1
UNKNOWN, // 2
YES_MANUAL, // 3
UNKNOWN, // 4
YES_MANUAL, // 5
YES_MANUAL, // 6
UNKNOWN, // 7
UNKNOWN, // 1
UNKNOWN, // 2
YES_MANUAL, // 3
UNKNOWN, // 4
YES_MANUAL, // 5
YES_MANUAL, // 6
UNKNOWN, // 7
)
assertThat(entries.getValues(today.minus(7), today.minus(1)), equalTo(expected))
}
@@ -98,22 +99,21 @@ class EntryListTest {
computed.recomputeFrom(original, Frequency(1, 3), isNumerical = false)
val expected = listOf(
Entry(today.minus(2), YES_AUTO),
Entry(today.minus(3), YES_AUTO),
Entry(today.minus(4), YES_MANUAL),
Entry(today.minus(7), YES_AUTO),
Entry(today.minus(8), YES_AUTO),
Entry(today.minus(9), YES_MANUAL),
Entry(today.minus(10), YES_MANUAL),
Entry(today.minus(11), YES_AUTO),
Entry(today.minus(12), YES_AUTO),
Entry(today.minus(2), YES_AUTO),
Entry(today.minus(3), YES_AUTO),
Entry(today.minus(4), YES_MANUAL),
Entry(today.minus(7), YES_AUTO),
Entry(today.minus(8), YES_AUTO),
Entry(today.minus(9), YES_MANUAL),
Entry(today.minus(10), YES_MANUAL),
Entry(today.minus(11), YES_AUTO),
Entry(today.minus(12), YES_AUTO),
)
assertEquals(expected, computed.getKnown())
// Second call should replace all previously added entries
computed.recomputeFrom(EntryList(), Frequency(1, 3), isNumerical = false)
assertEquals(listOf(), computed.getKnown())
}
@Test
@@ -129,9 +129,9 @@ class EntryListTest {
computed.recomputeFrom(original, Frequency.DAILY, isNumerical = true)
val expected = listOf(
Entry(today.minus(4), 100),
Entry(today.minus(9), 200),
Entry(today.minus(10), 300),
Entry(today.minus(4), 100),
Entry(today.minus(9), 200),
Entry(today.minus(10), 300),
)
assertEquals(expected, computed.getKnown())
}
@@ -139,21 +139,23 @@ class EntryListTest {
@Test
fun testGroupByNumerical() {
val offsets = intArrayOf(
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507)
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507
)
val values = intArrayOf(
230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124,
301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354,
236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168,
374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294,
158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219,
272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256,
370, 187, 208, 231, 341, 312)
230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124,
301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354,
236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168,
374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294,
158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219,
272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256,
370, 187, 208, 231, 341, 312
)
val reference = Timestamp.from(2014, Calendar.JUNE, 1)
val entries = EntryList()
@@ -162,10 +164,10 @@ class EntryListTest {
}
val byMonth = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
original = entries.getKnown(),
field = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
)
assertThat(byMonth.size, equalTo(17))
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 230)))
@@ -173,10 +175,10 @@ class EntryListTest {
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 1271)))
val byQuarter = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
original = entries.getKnown(),
field = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
)
assertThat(byQuarter.size, equalTo(6))
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 3263)))
@@ -184,10 +186,10 @@ class EntryListTest {
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 4975)))
val byYear = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
original = entries.getKnown(),
field = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = true,
)
assertThat(byYear.size, equalTo(2))
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 8227)))
@@ -197,12 +199,13 @@ class EntryListTest {
@Test
fun testGroupByBoolean() {
val offsets = intArrayOf(
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507)
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507
)
val reference = Timestamp.from(2014, Calendar.JUNE, 1)
val entries = EntryList()
@@ -211,10 +214,10 @@ class EntryListTest {
}
val byMonth = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
original = entries.getKnown(),
field = DateUtils.TruncateField.MONTH,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
)
assertThat(byMonth.size, equalTo(17))
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 1_000)))
@@ -222,10 +225,10 @@ class EntryListTest {
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 6_000)))
val byQuarter = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
original = entries.getKnown(),
field = DateUtils.TruncateField.QUARTER,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
)
assertThat(byQuarter.size, equalTo(6))
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 15_000)))
@@ -233,10 +236,10 @@ class EntryListTest {
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 20_000)))
val byYear = entries.groupBy(
original = entries.getKnown(),
field = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
original = entries.getKnown(),
field = DateUtils.TruncateField.YEAR,
firstWeekday = Calendar.SATURDAY,
isNumerical = false,
)
assertThat(byYear.size, equalTo(2))
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 34_000)))
@@ -246,30 +249,30 @@ class EntryListTest {
@Test
fun testAddFromInterval() {
val entries = listOf(
Entry(day(1), YES_MANUAL),
Entry(day(2), NO),
Entry(day(4), NO),
Entry(day(5), YES_MANUAL),
Entry(day(10), YES_MANUAL),
Entry(day(11), NO),
Entry(day(1), YES_MANUAL),
Entry(day(2), NO),
Entry(day(4), NO),
Entry(day(5), YES_MANUAL),
Entry(day(10), YES_MANUAL),
Entry(day(11), NO),
)
val intervals = listOf(
EntryList.Interval(day(2), day(2), day(1)),
EntryList.Interval(day(6), day(5), day(4)),
EntryList.Interval(day(10), day(8), day(8)),
EntryList.Interval(day(2), day(2), day(1)),
EntryList.Interval(day(6), day(5), day(4)),
EntryList.Interval(day(10), day(8), day(8)),
)
val expected = listOf(
Entry(day(1), YES_MANUAL),
Entry(day(2), YES_AUTO),
Entry(day(3), UNKNOWN),
Entry(day(4), YES_AUTO),
Entry(day(5), YES_MANUAL),
Entry(day(6), YES_AUTO),
Entry(day(7), UNKNOWN),
Entry(day(8), YES_AUTO),
Entry(day(9), YES_AUTO),
Entry(day(10), YES_MANUAL),
Entry(day(11), NO),
Entry(day(1), YES_MANUAL),
Entry(day(2), YES_AUTO),
Entry(day(3), UNKNOWN),
Entry(day(4), YES_AUTO),
Entry(day(5), YES_MANUAL),
Entry(day(6), YES_AUTO),
Entry(day(7), UNKNOWN),
Entry(day(8), YES_AUTO),
Entry(day(9), YES_AUTO),
Entry(day(10), YES_MANUAL),
Entry(day(11), NO),
)
val actual = EntryList.buildEntriesFromInterval(entries, intervals)
assertThat(actual, equalTo(expected))
@@ -278,16 +281,16 @@ class EntryListTest {
@Test
fun testSnapIntervalsTogether1() {
val original = arrayListOf(
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(12), day(12), day(6)),
EntryList.Interval(day(20), day(20), day(14)),
EntryList.Interval(day(27), day(27), day(21)),
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(12), day(12), day(6)),
EntryList.Interval(day(20), day(20), day(14)),
EntryList.Interval(day(27), day(27), day(21)),
)
val expected = arrayListOf(
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(15), day(12), day(9)),
EntryList.Interval(day(22), day(20), day(16)),
EntryList.Interval(day(29), day(27), day(23)),
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(15), day(12), day(9)),
EntryList.Interval(day(22), day(20), day(16)),
EntryList.Interval(day(29), day(27), day(23)),
)
EntryList.snapIntervalsTogether(original)
assertThat(original, equalTo(expected))
@@ -296,12 +299,12 @@ class EntryListTest {
@Test
fun testSnapIntervalsTogether2() {
val original = arrayListOf(
EntryList.Interval(day(6), day(4), day(0)),
EntryList.Interval(day(11), day(8), day(5)),
EntryList.Interval(day(6), day(4), day(0)),
EntryList.Interval(day(11), day(8), day(5)),
)
val expected = arrayListOf(
EntryList.Interval(day(6), day(4), day(0)),
EntryList.Interval(day(13), day(8), day(7)),
EntryList.Interval(day(6), day(4), day(0)),
EntryList.Interval(day(13), day(8), day(7)),
)
EntryList.snapIntervalsTogether(original)
assertThat(original, equalTo(expected))
@@ -310,14 +313,14 @@ class EntryListTest {
@Test
fun testBuildIntervals1() {
val entries = listOf(
Entry(day(8), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(23), YES_MANUAL),
Entry(day(8), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(23), YES_MANUAL),
)
val expected = listOf(
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(18), day(18), day(12)),
EntryList.Interval(day(23), day(23), day(17)),
EntryList.Interval(day(8), day(8), day(2)),
EntryList.Interval(day(18), day(18), day(12)),
EntryList.Interval(day(23), day(23), day(17)),
)
val actual = EntryList.buildIntervals(Frequency.WEEKLY, entries)
assertThat(actual, equalTo(expected))
@@ -326,14 +329,14 @@ class EntryListTest {
@Test
fun testBuildIntervals2() {
val entries = listOf(
Entry(day(8), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(23), YES_MANUAL),
Entry(day(8), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(23), YES_MANUAL),
)
val expected = listOf(
EntryList.Interval(day(8), day(8), day(8)),
EntryList.Interval(day(18), day(18), day(18)),
EntryList.Interval(day(23), day(23), day(23)),
EntryList.Interval(day(8), day(8), day(8)),
EntryList.Interval(day(18), day(18), day(18)),
EntryList.Interval(day(23), day(23), day(23)),
)
val actual = EntryList.buildIntervals(Frequency.DAILY, entries)
assertThat(actual, equalTo(expected))
@@ -342,32 +345,31 @@ class EntryListTest {
@Test
fun testBuildIntervals3() {
val entries = listOf(
Entry(day(8), YES_MANUAL),
Entry(day(15), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(22), YES_MANUAL),
Entry(day(23), YES_MANUAL),
Entry(day(8), YES_MANUAL),
Entry(day(15), YES_MANUAL),
Entry(day(18), YES_MANUAL),
Entry(day(22), YES_MANUAL),
Entry(day(23), YES_MANUAL),
)
val expected = listOf(
EntryList.Interval(day(18), day(15), day(12)),
EntryList.Interval(day(22), day(18), day(16)),
EntryList.Interval(day(23), day(22), day(17)),
EntryList.Interval(day(18), day(15), day(12)),
EntryList.Interval(day(22), day(18), day(16)),
EntryList.Interval(day(23), day(22), day(17)),
)
val actual = EntryList.buildIntervals(Frequency.TWO_TIMES_PER_WEEK, entries)
assertThat(actual, equalTo(expected))
}
@Test
fun testBuildIntervals4() {
val entries = listOf(
Entry(day(10), YES_MANUAL),
Entry(day(20), Entry.SKIP),
Entry(day(30), YES_MANUAL),
Entry(day(10), YES_MANUAL),
Entry(day(20), Entry.SKIP),
Entry(day(30), YES_MANUAL),
)
val expected = listOf(
EntryList.Interval(day(10), day(10), day(8)),
EntryList.Interval(day(30), day(30), day(28)),
EntryList.Interval(day(10), day(10), day(8)),
EntryList.Interval(day(30), day(30), day(28)),
)
val actual = EntryList.buildIntervals(Frequency(1, 3), entries)
assertThat(actual, equalTo(expected))
@@ -413,5 +415,4 @@ class EntryListTest {
}
fun day(offset: Int) = DateUtils.getToday().minus(offset)
}

View File

@@ -19,14 +19,18 @@
package org.isoron.uhabits.core.models.sqlite
import junit.framework.Assert.*
import org.isoron.uhabits.core.BaseUnitTest.*
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import org.isoron.uhabits.core.BaseUnitTest.buildMemoryDatabase
import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.sqlite.records.*
import org.isoron.uhabits.core.utils.*
import org.junit.*
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.utils.DateUtils
import org.junit.Before
import org.junit.Test
class SQLiteEntryListTest {
@@ -48,27 +52,31 @@ class SQLiteEntryListTest {
@Test
fun testLoad() {
val today = DateUtils.getToday()
repository.save(EntryRecord().apply {
habitId = entries.habitId
timestamp = today.unixTime
value = 500
})
repository.save(EntryRecord().apply {
habitId = entries.habitId
timestamp = today.minus(5).unixTime
value = 300
})
assertEquals(
Entry(timestamp = today, value = 500),
entries.get(today),
repository.save(
EntryRecord().apply {
habitId = entries.habitId
timestamp = today.unixTime
value = 500
}
)
repository.save(
EntryRecord().apply {
habitId = entries.habitId
timestamp = today.minus(5).unixTime
value = 300
}
)
assertEquals(
Entry(timestamp = today.minus(1), value = UNKNOWN),
entries.get(today.minus(1)),
Entry(timestamp = today, value = 500),
entries.get(today),
)
assertEquals(
Entry(timestamp = today.minus(5), value = 300),
entries.get(today.minus(5)),
Entry(timestamp = today.minus(1), value = UNKNOWN),
entries.get(today.minus(1)),
)
assertEquals(
Entry(timestamp = today.minus(5), value = 300),
entries.get(today.minus(5)),
)
}
@@ -92,14 +100,13 @@ class SQLiteEntryListTest {
}
private fun getByTimestamp(
habitId: Int,
timestamp: Timestamp,
habitId: Int,
timestamp: Timestamp,
): EntryRecord? {
return repository.findFirst(
"where habit = ? and timestamp = ?",
habitId.toString(),
timestamp.unixTime.toString(),
"where habit = ? and timestamp = ?",
habitId.toString(),
timestamp.unixTime.toString(),
)
}
}
}

View File

@@ -18,12 +18,15 @@
*/
package org.isoron.uhabits.core.sync
import kotlinx.coroutines.*
import org.hamcrest.Matchers.*
import org.junit.*
import org.junit.Assert.*
import java.io.*
import java.util.*
import kotlinx.coroutines.runBlocking
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.greaterThan
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThat
import org.junit.Test
import java.io.File
import java.io.PrintWriter
import java.util.Random
class EncryptionExtTest {