mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 04:58:52 -06:00
Creating the Habitgroup model
This commit is contained in:
@@ -79,6 +79,10 @@ class HabitsApplication : Application() {
|
||||
val habitList = component.habitList
|
||||
for (h in habitList) h.recompute()
|
||||
|
||||
val habitGroupList = component.habitGroupList
|
||||
for (hgr in habitGroupList) hgr.recompute()
|
||||
habitGroupList.populateGroupsWith(habitList)
|
||||
|
||||
widgetUpdater = component.widgetUpdater.apply {
|
||||
startListening()
|
||||
scheduleStartDayWidgetUpdate()
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.isoron.uhabits.core.AppScope
|
||||
import org.isoron.uhabits.core.commands.CommandRunner
|
||||
import org.isoron.uhabits.core.io.GenericImporter
|
||||
import org.isoron.uhabits.core.io.Logging
|
||||
import org.isoron.uhabits.core.models.HabitGroupList
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.ModelFactory
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
@@ -50,6 +51,7 @@ interface HabitsApplicationComponent {
|
||||
val genericImporter: GenericImporter
|
||||
val habitCardListCache: HabitCardListCache
|
||||
val habitList: HabitList
|
||||
val habitGroupList: HabitGroupList
|
||||
val intentFactory: IntentFactory
|
||||
val intentParser: IntentParser
|
||||
val logging: Logging
|
||||
|
||||
@@ -26,9 +26,11 @@ import org.isoron.uhabits.core.commands.CommandRunner
|
||||
import org.isoron.uhabits.core.database.Database
|
||||
import org.isoron.uhabits.core.database.DatabaseOpener
|
||||
import org.isoron.uhabits.core.io.Logging
|
||||
import org.isoron.uhabits.core.models.HabitGroupList
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.ModelFactory
|
||||
import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
|
||||
import org.isoron.uhabits.core.models.sqlite.SQLiteHabitGroupList
|
||||
import org.isoron.uhabits.core.models.sqlite.SQLiteHabitList
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||
@@ -97,6 +99,12 @@ class HabitsModule(dbFile: File) {
|
||||
return list
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppScope
|
||||
fun getHabitGroupList(list: SQLiteHabitGroupList): HabitGroupList {
|
||||
return list
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppScope
|
||||
fun getDatabaseOpener(opener: AndroidDatabaseOpener): DatabaseOpener {
|
||||
|
||||
@@ -40,12 +40,15 @@ data class Habit(
|
||||
val computedEntries: EntryList,
|
||||
val originalEntries: EntryList,
|
||||
val scores: ScoreList,
|
||||
val streaks: StreakList
|
||||
val streaks: StreakList,
|
||||
var parentID: Long? = null,
|
||||
var parentUUID: String? = null
|
||||
) {
|
||||
init {
|
||||
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
|
||||
}
|
||||
|
||||
var parent: HabitGroup? = null
|
||||
var observable = ModelObservable()
|
||||
|
||||
val isNumerical: Boolean
|
||||
@@ -111,6 +114,14 @@ data class Habit(
|
||||
return computedEntries.getKnown().lastOrNull()?.timestamp ?: DateUtils.getTodayWithOffset()
|
||||
}
|
||||
|
||||
fun hierarchyLevel(): Int {
|
||||
return if (parentID == null) {
|
||||
0
|
||||
} else {
|
||||
1 + parent!!.hierarchyLevel()
|
||||
}
|
||||
}
|
||||
|
||||
fun copyFrom(other: Habit) {
|
||||
this.color = other.color
|
||||
this.description = other.description
|
||||
@@ -127,6 +138,8 @@ data class Habit(
|
||||
this.type = other.type
|
||||
this.unit = other.unit
|
||||
this.uuid = other.uuid
|
||||
this.parentID = other.parentID
|
||||
this.parentUUID = other.parentUUID
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -148,6 +161,8 @@ data class Habit(
|
||||
if (type != other.type) return false
|
||||
if (unit != other.unit) return false
|
||||
if (uuid != other.uuid) return false
|
||||
if (parentID != other.parentID) return false
|
||||
if (parentUUID != other.parentUUID) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -168,6 +183,8 @@ data class Habit(
|
||||
result = 31 * result + type.value
|
||||
result = 31 * result + unit.hashCode()
|
||||
result = 31 * result + (uuid?.hashCode() ?: 0)
|
||||
result = 31 * result + (parentID?.hashCode() ?: 0)
|
||||
result = 31 * result + (parentUUID?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,19 @@ data class HabitGroup(
|
||||
var position: Int = 0,
|
||||
var question: String = "",
|
||||
var reminder: Reminder? = null,
|
||||
var unit: String = "",
|
||||
var uuid: String? = null,
|
||||
var habitList: HabitList,
|
||||
var habitGroupList: HabitGroupList,
|
||||
val scores: ScoreList,
|
||||
val streaks: StreakList
|
||||
val streaks: StreakList,
|
||||
var parentID: Long? = null,
|
||||
var parentUUID: String? = null
|
||||
) {
|
||||
init {
|
||||
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
|
||||
}
|
||||
|
||||
var parent: HabitGroup? = null
|
||||
var observable = ModelObservable()
|
||||
|
||||
val uriString: String
|
||||
@@ -76,7 +78,7 @@ data class HabitGroup(
|
||||
)
|
||||
}
|
||||
|
||||
fun copyFrom(other: Habit) {
|
||||
fun copyFrom(other: HabitGroup) {
|
||||
this.color = other.color
|
||||
this.description = other.description
|
||||
// this.id should not be copied
|
||||
@@ -85,8 +87,9 @@ data class HabitGroup(
|
||||
this.position = other.position
|
||||
this.question = other.question
|
||||
this.reminder = other.reminder
|
||||
this.unit = other.unit
|
||||
this.uuid = other.uuid
|
||||
this.parentID = other.parentID
|
||||
this.parentUUID = other.parentUUID
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -101,8 +104,9 @@ data class HabitGroup(
|
||||
if (position != other.position) return false
|
||||
if (question != other.question) return false
|
||||
if (reminder != other.reminder) return false
|
||||
if (unit != other.unit) return false
|
||||
if (uuid != other.uuid) return false
|
||||
if (parentID != other.parentID) return false
|
||||
if (parentUUID != other.parentUUID) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -116,8 +120,37 @@ data class HabitGroup(
|
||||
result = 31 * result + position
|
||||
result = 31 * result + question.hashCode()
|
||||
result = 31 * result + (reminder?.hashCode() ?: 0)
|
||||
result = 31 * result + unit.hashCode()
|
||||
result = 31 * result + (uuid?.hashCode() ?: 0)
|
||||
result = 31 * result + (parentID?.hashCode() ?: 0)
|
||||
result = 31 * result + (parentUUID?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
fun hierarchyLevel(): Int {
|
||||
return if (parentID == null) {
|
||||
0
|
||||
} else {
|
||||
1 + parent!!.hierarchyLevel()
|
||||
}
|
||||
}
|
||||
|
||||
fun getHabitByUUIDDeep(uuid: String?): Habit? {
|
||||
val habit = habitList.getByUUID(uuid)
|
||||
if (habit != null) return habit
|
||||
for (hgr in habitGroupList) {
|
||||
val found = hgr.getHabitByUUIDDeep(uuid)
|
||||
if (found != null) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getHabitGroupByUUIDDeep(uuid: String?): HabitGroup? {
|
||||
val habitGroup = habitGroupList.getByUUID(uuid)
|
||||
if (habitGroup != null) return habitGroup
|
||||
for (hgr in habitGroupList) {
|
||||
val found = hgr.getHabitGroupByUUIDDeep(uuid)
|
||||
if (found != null) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import com.opencsv.CSVWriter
|
||||
import org.isoron.uhabits.core.models.HabitList.Order
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
import java.util.LinkedList
|
||||
@@ -63,6 +64,30 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
|
||||
*/
|
||||
abstract fun getByUUID(uuid: String?): HabitGroup?
|
||||
|
||||
/**
|
||||
* Returns the habit with the specified UUID which is
|
||||
* present at any hierarchy within this list.
|
||||
*/
|
||||
fun getHabitByUUIDDeep(uuid: String?): Habit? {
|
||||
for (hgr in this) {
|
||||
val habit = hgr.getHabitByUUIDDeep(uuid)
|
||||
if (habit != null) {
|
||||
return habit
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getHabitGroupByUUIDDeep(uuid: String?): HabitGroup? {
|
||||
for (hgr in this) {
|
||||
val habit = hgr.getHabitGroupByUUIDDeep(uuid)
|
||||
if (habit != null) {
|
||||
return habit
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the habit that occupies a certain position.
|
||||
*
|
||||
@@ -150,6 +175,42 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
|
||||
update(listOf(habitGroup))
|
||||
}
|
||||
|
||||
fun populateGroupsWith(habitList: HabitList) {
|
||||
val toRemove = mutableListOf<String?>()
|
||||
for (habit in habitList) {
|
||||
val hgr = getByUUID(habit.parentUUID)
|
||||
if (hgr != null) {
|
||||
hgr.habitList.add(habit)
|
||||
habit.parent = hgr
|
||||
toRemove.add(habit.uuid)
|
||||
}
|
||||
}
|
||||
for (uuid in toRemove) {
|
||||
val h = habitList.getByUUID(uuid)
|
||||
if (h != null) {
|
||||
habitList.remove(h)
|
||||
}
|
||||
}
|
||||
toRemove.clear()
|
||||
for (hgr1 in this) {
|
||||
val hgr2 = getByUUID(hgr1.parentUUID)
|
||||
if (hgr2 != null) {
|
||||
hgr2.habitGroupList.add(hgr1)
|
||||
toRemove.add(hgr1.uuid)
|
||||
hgr1.parent = hgr2
|
||||
}
|
||||
}
|
||||
for (uuid in toRemove) {
|
||||
val h = getByUUID(uuid)
|
||||
if (h != null) {
|
||||
remove(h)
|
||||
}
|
||||
}
|
||||
for (hgr in this) {
|
||||
hgr.recompute()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the list of habits to the given writer, in CSV format. There is
|
||||
* one line for each habit, containing the fields name, description,
|
||||
@@ -186,15 +247,4 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
|
||||
}
|
||||
|
||||
abstract fun resort()
|
||||
enum class Order {
|
||||
BY_NAME_ASC,
|
||||
BY_NAME_DESC,
|
||||
BY_COLOR_ASC,
|
||||
BY_COLOR_DESC,
|
||||
BY_SCORE_ASC,
|
||||
BY_SCORE_DESC,
|
||||
BY_STATUS_ASC,
|
||||
BY_STATUS_DESC,
|
||||
BY_POSITION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ data class HabitMatcher(
|
||||
return true
|
||||
}
|
||||
|
||||
fun matches(habitGroup: HabitGroup): Boolean {
|
||||
if (!isArchivedAllowed && habitGroup.isArchived) return false
|
||||
if (isReminderRequired && !habitGroup.hasReminder()) return false
|
||||
if (!isCompletedAllowed && habitGroup.isCompletedToday()) return false
|
||||
if (!isEnteredAllowed && habitGroup.isEnteredToday()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val WITH_ALARM = HabitMatcher(
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.isoron.uhabits.core.models
|
||||
|
||||
import org.isoron.uhabits.core.database.Repository
|
||||
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
|
||||
import org.isoron.uhabits.core.models.sqlite.records.HabitGroupRecord
|
||||
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
|
||||
|
||||
/**
|
||||
@@ -38,11 +39,25 @@ interface ModelFactory {
|
||||
computedEntries = buildComputedEntries()
|
||||
)
|
||||
}
|
||||
fun buildHabitGroup(): HabitGroup {
|
||||
val habits = buildHabitList()
|
||||
val groups = buildHabitGroupList()
|
||||
val scores = buildScoreList()
|
||||
val streaks = buildStreakList()
|
||||
return HabitGroup(
|
||||
habitList = habits,
|
||||
habitGroupList = groups,
|
||||
scores = scores,
|
||||
streaks = streaks
|
||||
)
|
||||
}
|
||||
fun buildComputedEntries(): EntryList
|
||||
fun buildOriginalEntries(): EntryList
|
||||
fun buildHabitList(): HabitList
|
||||
fun buildHabitGroupList(): HabitGroupList
|
||||
fun buildScoreList(): ScoreList
|
||||
fun buildStreakList(): StreakList
|
||||
fun buildHabitListRepository(): Repository<HabitRecord>
|
||||
fun buildRepetitionListRepository(): Repository<EntryRecord>
|
||||
fun buildHabitGroupListRepository(): Repository<HabitGroupRecord>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
package org.isoron.uhabits.core.models.memory
|
||||
|
||||
import org.isoron.uhabits.core.models.HabitGroup
|
||||
import org.isoron.uhabits.core.models.HabitGroupList
|
||||
import org.isoron.uhabits.core.models.HabitList.Order
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
||||
import java.util.LinkedList
|
||||
import java.util.Objects
|
||||
|
||||
/**
|
||||
* In-memory implementation of [HabitGroupList].
|
||||
*/
|
||||
class MemoryHabitGroupList : HabitGroupList {
|
||||
private val list = LinkedList<HabitGroup>()
|
||||
|
||||
@get:Synchronized
|
||||
override var primaryOrder = Order.BY_POSITION
|
||||
set(value) {
|
||||
field = value
|
||||
comparator = getComposedComparatorByOrder(primaryOrder, secondaryOrder)
|
||||
resort()
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
override var secondaryOrder = Order.BY_NAME_ASC
|
||||
set(value) {
|
||||
field = value
|
||||
comparator = getComposedComparatorByOrder(primaryOrder, secondaryOrder)
|
||||
resort()
|
||||
}
|
||||
|
||||
private var comparator: Comparator<HabitGroup>? =
|
||||
getComposedComparatorByOrder(primaryOrder, secondaryOrder)
|
||||
private var parent: MemoryHabitGroupList? = null
|
||||
|
||||
constructor() : super()
|
||||
constructor(
|
||||
matcher: HabitMatcher,
|
||||
comparator: Comparator<HabitGroup>?,
|
||||
parent: MemoryHabitGroupList
|
||||
) : super(matcher) {
|
||||
this.parent = parent
|
||||
this.comparator = comparator
|
||||
primaryOrder = parent.primaryOrder
|
||||
secondaryOrder = parent.secondaryOrder
|
||||
parent.observable.addListener { loadFromParent() }
|
||||
loadFromParent()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override fun add(habitGroup: HabitGroup) {
|
||||
throwIfHasParent()
|
||||
require(!list.contains(habitGroup)) { "habit already added" }
|
||||
val id = habitGroup.id
|
||||
if (id != null && getById(id) != null) throw RuntimeException("duplicate id")
|
||||
if (id == null) habitGroup.id = list.size.toLong()
|
||||
list.addLast(habitGroup)
|
||||
resort()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getById(id: Long): HabitGroup? {
|
||||
for (h in list) {
|
||||
checkNotNull(h.id)
|
||||
if (h.id == id) return h
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getByUUID(uuid: String?): HabitGroup? {
|
||||
for (h in list) if (Objects.requireNonNull(h.uuid) == uuid) return h
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getByPosition(position: Int): HabitGroup {
|
||||
return list[position]
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getFiltered(matcher: HabitMatcher?): HabitGroupList {
|
||||
return MemoryHabitGroupList(matcher!!, comparator, this)
|
||||
}
|
||||
|
||||
private fun getComposedComparatorByOrder(
|
||||
firstOrder: Order,
|
||||
secondOrder: Order?
|
||||
): Comparator<HabitGroup> {
|
||||
return Comparator { h1: HabitGroup, h2: HabitGroup ->
|
||||
val firstResult = getComparatorByOrder(firstOrder).compare(h1, h2)
|
||||
if (firstResult != 0 || secondOrder == null) {
|
||||
return@Comparator firstResult
|
||||
}
|
||||
getComparatorByOrder(secondOrder).compare(h1, h2)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getComparatorByOrder(order: Order): Comparator<HabitGroup> {
|
||||
val nameComparatorAsc = Comparator<HabitGroup> { habit1, habit2 ->
|
||||
habit1.name.compareTo(habit2.name)
|
||||
}
|
||||
val nameComparatorDesc =
|
||||
Comparator { h1: HabitGroup, h2: HabitGroup -> nameComparatorAsc.compare(h2, h1) }
|
||||
val colorComparatorAsc = Comparator<HabitGroup> { (color1), (color2) ->
|
||||
color1.compareTo(color2)
|
||||
}
|
||||
val colorComparatorDesc =
|
||||
Comparator { h1: HabitGroup, h2: HabitGroup -> colorComparatorAsc.compare(h2, h1) }
|
||||
val scoreComparatorDesc =
|
||||
Comparator<HabitGroup> { habit1, habit2 ->
|
||||
val today = getTodayWithOffset()
|
||||
habit1.scores[today].value.compareTo(habit2.scores[today].value)
|
||||
}
|
||||
val scoreComparatorAsc =
|
||||
Comparator { h1: HabitGroup, h2: HabitGroup -> scoreComparatorDesc.compare(h2, h1) }
|
||||
val positionComparator =
|
||||
Comparator<HabitGroup> { habit1, habit2 -> habit1.position.compareTo(habit2.position) }
|
||||
val statusComparatorDesc = Comparator { h1: HabitGroup, h2: HabitGroup ->
|
||||
if (h1.isCompletedToday() != h2.isCompletedToday()) {
|
||||
return@Comparator if (h1.isCompletedToday()) -1 else 1
|
||||
}
|
||||
val today = getTodayWithOffset()
|
||||
val v1 = h1.scores[today].value
|
||||
val v2 = h2.scores[today].value
|
||||
v2.compareTo(v1)
|
||||
}
|
||||
val statusComparatorAsc =
|
||||
Comparator { h1: HabitGroup, h2: HabitGroup -> statusComparatorDesc.compare(h2, h1) }
|
||||
return when {
|
||||
order === Order.BY_POSITION -> positionComparator
|
||||
order === Order.BY_NAME_ASC -> nameComparatorAsc
|
||||
order === Order.BY_NAME_DESC -> nameComparatorDesc
|
||||
order === Order.BY_COLOR_ASC -> colorComparatorAsc
|
||||
order === Order.BY_COLOR_DESC -> colorComparatorDesc
|
||||
order === Order.BY_SCORE_DESC -> scoreComparatorDesc
|
||||
order === Order.BY_SCORE_ASC -> scoreComparatorAsc
|
||||
order === Order.BY_STATUS_DESC -> statusComparatorDesc
|
||||
order === Order.BY_STATUS_ASC -> statusComparatorAsc
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun indexOf(h: HabitGroup): Int {
|
||||
return list.indexOf(h)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun iterator(): Iterator<HabitGroup> {
|
||||
return ArrayList(list).iterator()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun remove(h: HabitGroup) {
|
||||
throwIfHasParent()
|
||||
list.remove(h)
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun reorder(from: HabitGroup, to: HabitGroup) {
|
||||
throwIfHasParent()
|
||||
check(!(primaryOrder !== Order.BY_POSITION)) { "cannot reorder automatically sorted list" }
|
||||
require(indexOf(from) >= 0) { "list does not contain (from) habit" }
|
||||
val toPos = indexOf(to)
|
||||
require(toPos >= 0) { "list does not contain (to) habit" }
|
||||
list.remove(from)
|
||||
list.add(toPos, from)
|
||||
var position = 0
|
||||
for (h in list) h.position = position++
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun size(): Int {
|
||||
return list.size
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun update(habitGroups: List<HabitGroup>) {
|
||||
resort()
|
||||
}
|
||||
|
||||
private fun throwIfHasParent() {
|
||||
check(parent == null) {
|
||||
"Filtered lists cannot be modified directly. " +
|
||||
"You should modify the parent list instead."
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun loadFromParent() {
|
||||
checkNotNull(parent)
|
||||
list.clear()
|
||||
for (h in parent!!) if (filter.matches(h)) list.add(h)
|
||||
resort()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun resort() {
|
||||
for (hgr in list) {
|
||||
hgr.habitList.primaryOrder = primaryOrder
|
||||
hgr.habitList.secondaryOrder = secondaryOrder
|
||||
hgr.habitList.resort()
|
||||
|
||||
hgr.habitGroupList.primaryOrder = primaryOrder
|
||||
hgr.habitGroupList.secondaryOrder = secondaryOrder
|
||||
hgr.habitGroupList.resort()
|
||||
}
|
||||
if (comparator != null) list.sortWith(comparator!!)
|
||||
observable.notifyListeners()
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,10 @@ class MemoryModelFactory : ModelFactory {
|
||||
override fun buildComputedEntries() = EntryList()
|
||||
override fun buildOriginalEntries() = EntryList()
|
||||
override fun buildHabitList() = MemoryHabitList()
|
||||
override fun buildHabitGroupList() = MemoryHabitGroupList()
|
||||
override fun buildScoreList() = ScoreList()
|
||||
override fun buildStreakList() = StreakList()
|
||||
override fun buildHabitListRepository() = throw NotImplementedError()
|
||||
override fun buildRepetitionListRepository() = throw NotImplementedError()
|
||||
override fun buildHabitGroupListRepository() = throw NotImplementedError()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.HabitGroupRecord
|
||||
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -38,6 +39,8 @@ class SQLModelFactory
|
||||
override fun buildOriginalEntries() = SQLiteEntryList(database)
|
||||
override fun buildComputedEntries() = EntryList()
|
||||
override fun buildHabitList() = SQLiteHabitList(this)
|
||||
override fun buildHabitGroupList() = SQLiteHabitGroupList(this)
|
||||
|
||||
override fun buildScoreList() = ScoreList()
|
||||
override fun buildStreakList() = StreakList()
|
||||
|
||||
@@ -46,4 +49,7 @@ class SQLModelFactory
|
||||
|
||||
override fun buildRepetitionListRepository() =
|
||||
Repository(EntryRecord::class.java, database)
|
||||
|
||||
override fun buildHabitGroupListRepository() =
|
||||
Repository(HabitGroupRecord::class.java, database)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package org.isoron.uhabits.core.models.sqlite
|
||||
|
||||
import org.isoron.uhabits.core.database.Repository
|
||||
import org.isoron.uhabits.core.models.HabitGroup
|
||||
import org.isoron.uhabits.core.models.HabitGroupList
|
||||
import org.isoron.uhabits.core.models.HabitList.Order
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
import org.isoron.uhabits.core.models.ModelFactory
|
||||
import org.isoron.uhabits.core.models.memory.MemoryHabitGroupList
|
||||
import org.isoron.uhabits.core.models.sqlite.records.HabitGroupRecord
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Implementation of a [HabitGroupList] that is backed by SQLite.
|
||||
*/
|
||||
class SQLiteHabitGroupList @Inject constructor(private val modelFactory: ModelFactory) : HabitGroupList() {
|
||||
private val repository: Repository<HabitGroupRecord> = modelFactory.buildHabitGroupListRepository()
|
||||
private val list: MemoryHabitGroupList = MemoryHabitGroupList()
|
||||
private var loaded = false
|
||||
private fun loadRecords() {
|
||||
if (loaded) return
|
||||
loaded = true
|
||||
list.removeAll()
|
||||
val records = repository.findAll("order by position")
|
||||
var shouldRebuildOrder = false
|
||||
for ((expectedPosition, rec) in records.withIndex()) {
|
||||
if (rec.position != expectedPosition) shouldRebuildOrder = true
|
||||
val h = modelFactory.buildHabitGroup()
|
||||
rec.copyTo(h)
|
||||
list.add(h)
|
||||
}
|
||||
if (shouldRebuildOrder) rebuildOrder()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun add(habitGroup: HabitGroup) {
|
||||
loadRecords()
|
||||
habitGroup.position = size()
|
||||
val record = HabitGroupRecord()
|
||||
record.copyFrom(habitGroup)
|
||||
repository.save(record)
|
||||
habitGroup.id = record.id
|
||||
list.add(habitGroup)
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getById(id: Long): HabitGroup? {
|
||||
loadRecords()
|
||||
return list.getById(id)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getByUUID(uuid: String?): HabitGroup? {
|
||||
loadRecords()
|
||||
return list.getByUUID(uuid)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getByPosition(position: Int): HabitGroup {
|
||||
loadRecords()
|
||||
return list.getByPosition(position)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getFiltered(matcher: HabitMatcher?): HabitGroupList {
|
||||
loadRecords()
|
||||
return list.getFiltered(matcher)
|
||||
}
|
||||
|
||||
@set:Synchronized
|
||||
override var primaryOrder: Order
|
||||
get() = list.primaryOrder
|
||||
set(order) {
|
||||
list.primaryOrder = order
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@set:Synchronized
|
||||
override var secondaryOrder: Order
|
||||
get() = list.secondaryOrder
|
||||
set(order) {
|
||||
list.secondaryOrder = order
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun indexOf(h: HabitGroup): Int {
|
||||
loadRecords()
|
||||
return list.indexOf(h)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun iterator(): Iterator<HabitGroup> {
|
||||
loadRecords()
|
||||
return list.iterator()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun rebuildOrder() {
|
||||
val records = repository.findAll("order by position")
|
||||
repository.executeAsTransaction {
|
||||
for ((pos, r) in records.withIndex()) {
|
||||
if (r.position != pos) {
|
||||
r.position = pos
|
||||
repository.save(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun remove(h: HabitGroup) {
|
||||
loadRecords()
|
||||
list.remove(h)
|
||||
val record = repository.find(
|
||||
h.id!!
|
||||
) ?: throw RuntimeException("habit not in database")
|
||||
repository.executeAsTransaction {
|
||||
repository.remove(record)
|
||||
}
|
||||
rebuildOrder()
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeAll() {
|
||||
list.removeAll()
|
||||
repository.execSQL("delete from habits")
|
||||
repository.execSQL("delete from repetitions")
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun reorder(from: HabitGroup, to: HabitGroup) {
|
||||
loadRecords()
|
||||
list.reorder(from, to)
|
||||
val fromRecord = repository.find(
|
||||
from.id!!
|
||||
)
|
||||
val toRecord = repository.find(
|
||||
to.id!!
|
||||
)
|
||||
if (fromRecord == null) throw RuntimeException("habit not in database")
|
||||
if (toRecord == null) throw RuntimeException("habit not in database")
|
||||
if (toRecord.position!! < fromRecord.position!!) {
|
||||
repository.execSQL(
|
||||
"update habits set position = position + 1 " +
|
||||
"where position >= ? and position < ?",
|
||||
toRecord.position!!,
|
||||
fromRecord.position!!
|
||||
)
|
||||
} else {
|
||||
repository.execSQL(
|
||||
"update habits set position = position - 1 " +
|
||||
"where position > ? and position <= ?",
|
||||
fromRecord.position!!,
|
||||
toRecord.position!!
|
||||
)
|
||||
}
|
||||
fromRecord.position = toRecord.position
|
||||
repository.save(fromRecord)
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun repair() {
|
||||
loadRecords()
|
||||
rebuildOrder()
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun size(): Int {
|
||||
loadRecords()
|
||||
return list.size()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun update(habitGroups: List<HabitGroup>) {
|
||||
loadRecords()
|
||||
list.update(habitGroups)
|
||||
for (h in habitGroups) {
|
||||
val record = repository.find(h.id!!) ?: continue
|
||||
record.copyFrom(h)
|
||||
repository.save(record)
|
||||
}
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
override fun resort() {
|
||||
list.resort()
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun reload() {
|
||||
loaded = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.isoron.uhabits.core.models.sqlite.records
|
||||
|
||||
import org.isoron.uhabits.core.database.Column
|
||||
import org.isoron.uhabits.core.database.Table
|
||||
import org.isoron.uhabits.core.models.HabitGroup
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.models.Reminder
|
||||
import org.isoron.uhabits.core.models.WeekdayList
|
||||
import java.util.Objects.requireNonNull
|
||||
|
||||
/**
|
||||
* The SQLite database record corresponding to a [HabitGroup].
|
||||
*/
|
||||
@Table(name = "habitgroups")
|
||||
class HabitGroupRecord {
|
||||
@field:Column
|
||||
var description: String? = null
|
||||
|
||||
@field:Column
|
||||
var question: String? = null
|
||||
|
||||
@field:Column
|
||||
var name: String? = null
|
||||
|
||||
@field:Column
|
||||
var color: Int? = null
|
||||
|
||||
@field:Column
|
||||
var position: Int? = null
|
||||
|
||||
@field:Column(name = "reminder_hour")
|
||||
var reminderHour: Int? = null
|
||||
|
||||
@field:Column(name = "reminder_min")
|
||||
var reminderMin: Int? = null
|
||||
|
||||
@field:Column(name = "reminder_days")
|
||||
var reminderDays: Int? = null
|
||||
|
||||
@field:Column
|
||||
var highlight: Int? = null
|
||||
|
||||
@field:Column
|
||||
var archived: Int? = null
|
||||
|
||||
@field:Column
|
||||
var id: Long? = null
|
||||
|
||||
@field:Column
|
||||
var uuid: String? = null
|
||||
|
||||
@field:Column(name = "parent_id")
|
||||
var parentID: Long? = null
|
||||
|
||||
@field:Column(name = "parent_uuid")
|
||||
var parentUUID: String? = null
|
||||
|
||||
fun copyFrom(model: HabitGroup) {
|
||||
id = model.id
|
||||
name = model.name
|
||||
description = model.description
|
||||
highlight = 0
|
||||
color = model.color.paletteIndex
|
||||
archived = if (model.isArchived) 1 else 0
|
||||
position = model.position
|
||||
question = model.question
|
||||
uuid = model.uuid
|
||||
reminderDays = 0
|
||||
reminderMin = null
|
||||
reminderHour = null
|
||||
parentID = model.parentID
|
||||
parentUUID = model.parentUUID
|
||||
if (model.hasReminder()) {
|
||||
val reminder = model.reminder
|
||||
reminderHour = requireNonNull(reminder)!!.hour
|
||||
reminderMin = reminder!!.minute
|
||||
reminderDays = reminder.days.toInteger()
|
||||
}
|
||||
}
|
||||
|
||||
fun copyTo(habitGroup: HabitGroup) {
|
||||
habitGroup.id = id
|
||||
habitGroup.name = name!!
|
||||
habitGroup.description = description!!
|
||||
habitGroup.question = question!!
|
||||
habitGroup.color = PaletteColor(color!!)
|
||||
habitGroup.isArchived = archived != 0
|
||||
habitGroup.position = position!!
|
||||
habitGroup.uuid = uuid
|
||||
habitGroup.parentID = parentID
|
||||
habitGroup.parentUUID = parentUUID
|
||||
if (reminderHour != null && reminderMin != null) {
|
||||
habitGroup.reminder = Reminder(
|
||||
reminderHour!!,
|
||||
reminderMin!!,
|
||||
WeekdayList(reminderDays!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,12 @@ class HabitRecord {
|
||||
@field:Column
|
||||
var uuid: String? = null
|
||||
|
||||
@field:Column(name = "parent_id")
|
||||
var parentID: Long? = null
|
||||
|
||||
@field:Column(name = "parent_uuid")
|
||||
var parentUUID: String? = null
|
||||
|
||||
fun copyFrom(model: Habit) {
|
||||
id = model.id
|
||||
name = model.name
|
||||
@@ -109,6 +115,8 @@ class HabitRecord {
|
||||
position = model.position
|
||||
question = model.question
|
||||
uuid = model.uuid
|
||||
parentID = model.parentID
|
||||
parentUUID = model.parentUUID
|
||||
val (numerator, denominator) = model.frequency
|
||||
freqNum = numerator
|
||||
freqDen = denominator
|
||||
@@ -140,6 +148,8 @@ class HabitRecord {
|
||||
habit.unit = unit!!
|
||||
habit.position = position!!
|
||||
habit.uuid = uuid
|
||||
habit.parentID = parentID
|
||||
habit.parentUUID = parentUUID
|
||||
if (reminderHour != null && reminderMin != null) {
|
||||
habit.reminder = Reminder(
|
||||
reminderHour!!,
|
||||
|
||||
@@ -1,2 +1,21 @@
|
||||
alter table Habits add column skip_days integer not null default 0;
|
||||
alter table Habits add column skip_days_list integer not null default 0;
|
||||
alter table Habits add column skip_days_list integer not null default 0;
|
||||
alter table Habits add column parent_id integer;
|
||||
alter table Habits add column parent_uuid text;
|
||||
|
||||
create table HabitGroups (
|
||||
id integer primary key autoincrement,
|
||||
archived integer,
|
||||
color integer,
|
||||
description text not null default "",
|
||||
highlight integer,
|
||||
name text,
|
||||
position integer,
|
||||
reminder_days integer not null default 127,
|
||||
reminder_hour integer,
|
||||
reminder_min integer,
|
||||
question text not null default "",
|
||||
uuid text,
|
||||
parent_id integer,
|
||||
parent_uuid integer
|
||||
);
|
||||
Reference in New Issue
Block a user