mirror of https://github.com/iSoron/uhabits.git
parent
053bfe116a
commit
340bde9f69
@ -0,0 +1,123 @@
|
|||||||
|
package org.isoron.uhabits.core.models
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class HabitGroup(
|
||||||
|
var color: PaletteColor = PaletteColor(8),
|
||||||
|
var description: String = "",
|
||||||
|
var id: Long? = null,
|
||||||
|
var isArchived: Boolean = false,
|
||||||
|
var name: String = "",
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var observable = ModelObservable()
|
||||||
|
|
||||||
|
val uriString: String
|
||||||
|
get() = "content://org.isoron.uhabits/habit/$id"
|
||||||
|
|
||||||
|
fun hasReminder(): Boolean = reminder != null
|
||||||
|
|
||||||
|
fun isCompletedToday(): Boolean {
|
||||||
|
return habitList.all { it.isCompletedToday() } && habitGroupList.all { it.isCompletedToday() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEnteredToday(): Boolean {
|
||||||
|
return habitList.all { it.isEnteredToday() } && habitGroupList.all { it.isEnteredToday() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun firstEntryDate(): Timestamp {
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
var earliest = today
|
||||||
|
for (h in habitList) {
|
||||||
|
val first = h.firstEntryDate()
|
||||||
|
if (earliest.isNewerThan(first)) earliest = first
|
||||||
|
}
|
||||||
|
for (hgr in habitGroupList) {
|
||||||
|
val first = hgr.firstEntryDate()
|
||||||
|
if (earliest.isNewerThan(first)) earliest = first
|
||||||
|
}
|
||||||
|
return earliest
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recompute() {
|
||||||
|
for (h in habitList) h.recompute()
|
||||||
|
for (hgr in habitGroupList) hgr.recompute()
|
||||||
|
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
val to = today.plus(30)
|
||||||
|
var from = firstEntryDate()
|
||||||
|
if (from.isNewerThan(to)) from = to
|
||||||
|
|
||||||
|
scores.combineFrom(
|
||||||
|
habitList = habitList,
|
||||||
|
habitGroupList = habitGroupList,
|
||||||
|
from = from,
|
||||||
|
to = to
|
||||||
|
)
|
||||||
|
|
||||||
|
streaks.combineFrom(
|
||||||
|
habitList = habitList,
|
||||||
|
habitGroupList = habitGroupList,
|
||||||
|
from = from,
|
||||||
|
to = to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyFrom(other: Habit) {
|
||||||
|
this.color = other.color
|
||||||
|
this.description = other.description
|
||||||
|
// this.id should not be copied
|
||||||
|
this.isArchived = other.isArchived
|
||||||
|
this.name = other.name
|
||||||
|
this.position = other.position
|
||||||
|
this.question = other.question
|
||||||
|
this.reminder = other.reminder
|
||||||
|
this.unit = other.unit
|
||||||
|
this.uuid = other.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Habit) return false
|
||||||
|
|
||||||
|
if (color != other.color) return false
|
||||||
|
if (description != other.description) return false
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (isArchived != other.isArchived) return false
|
||||||
|
if (name != other.name) return false
|
||||||
|
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
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = color.hashCode()
|
||||||
|
result = 31 * result + description.hashCode()
|
||||||
|
result = 31 * result + (id?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + isArchived.hashCode()
|
||||||
|
result = 31 * result + name.hashCode()
|
||||||
|
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)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
package org.isoron.uhabits.core.models
|
||||||
|
|
||||||
|
import com.opencsv.CSVWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.Writer
|
||||||
|
import java.util.LinkedList
|
||||||
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ordered collection of [HabitGroup]s.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
abstract class HabitGroupList : Iterable<HabitGroup> {
|
||||||
|
val observable: ModelObservable
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected val filter: HabitMatcher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HabitList.
|
||||||
|
*
|
||||||
|
* Depending on the implementation, this list can either be empty or be
|
||||||
|
* populated by some pre-existing habits, for example, from a certain
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
observable = ModelObservable()
|
||||||
|
filter = HabitMatcher(isArchivedAllowed = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(filter: HabitMatcher) {
|
||||||
|
observable = ModelObservable()
|
||||||
|
this.filter = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new habit in the list.
|
||||||
|
*
|
||||||
|
* If the id of the habit is null, the list will assign it a new id, which
|
||||||
|
* is guaranteed to be unique in the scope of the list. If id is not null,
|
||||||
|
* the caller should make sure that the list does not already contain
|
||||||
|
* another habit with same id, otherwise a RuntimeException will be thrown.
|
||||||
|
*
|
||||||
|
* @param habitGroup the habit to be inserted
|
||||||
|
* @throws IllegalArgumentException if the habit is already on the list.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
abstract fun add(habitGroup: HabitGroup)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit with specified id.
|
||||||
|
*
|
||||||
|
* @param id the id of the habit
|
||||||
|
* @return the habit, or null if none exist
|
||||||
|
*/
|
||||||
|
abstract fun getById(id: Long): HabitGroup?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit with specified UUID.
|
||||||
|
*
|
||||||
|
* @param uuid the UUID of the habit
|
||||||
|
* @return the habit, or null if none exist
|
||||||
|
*/
|
||||||
|
abstract fun getByUUID(uuid: String?): HabitGroup?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit that occupies a certain position.
|
||||||
|
*
|
||||||
|
* @param position the position of the desired habit
|
||||||
|
* @return the habit at that position
|
||||||
|
* @throws IndexOutOfBoundsException when the position is invalid
|
||||||
|
*/
|
||||||
|
abstract fun getByPosition(position: Int): HabitGroup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of habits that match a given condition.
|
||||||
|
*
|
||||||
|
* @param matcher the matcher that checks the condition
|
||||||
|
* @return the list of matching habits
|
||||||
|
*/
|
||||||
|
abstract fun getFiltered(matcher: HabitMatcher?): HabitGroupList
|
||||||
|
abstract var primaryOrder: Order
|
||||||
|
abstract var secondaryOrder: Order
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the given habit in the list, or -1 if the list does
|
||||||
|
* not contain the habit.
|
||||||
|
*
|
||||||
|
* @param h the habit
|
||||||
|
* @return the index of the habit, or -1 if not in the list
|
||||||
|
*/
|
||||||
|
abstract fun indexOf(h: HabitGroup): Int
|
||||||
|
val isEmpty: Boolean
|
||||||
|
get() = size() == 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given habit from the list.
|
||||||
|
*
|
||||||
|
* If the given habit is not in the list, does nothing.
|
||||||
|
*
|
||||||
|
* @param h the habit to be removed.
|
||||||
|
*/
|
||||||
|
abstract fun remove(h: HabitGroup)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all the habits from the list.
|
||||||
|
*/
|
||||||
|
open fun removeAll() {
|
||||||
|
val copy: MutableList<HabitGroup> = LinkedList()
|
||||||
|
for (h in this) copy.add(h)
|
||||||
|
for (h in copy) remove(h)
|
||||||
|
observable.notifyListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the position of a habit in the list.
|
||||||
|
*
|
||||||
|
* @param from the habit that should be moved
|
||||||
|
* @param to the habit that currently occupies the desired position
|
||||||
|
*/
|
||||||
|
abstract fun reorder(from: HabitGroup, to: HabitGroup)
|
||||||
|
open fun repair() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of habits in this list.
|
||||||
|
*
|
||||||
|
* @return number of habits
|
||||||
|
*/
|
||||||
|
abstract fun size(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the list that a certain list of habits has been modified.
|
||||||
|
*
|
||||||
|
* Depending on the implementation, this operation might trigger a write to
|
||||||
|
* disk, or do nothing at all. To make sure that the habits get persisted,
|
||||||
|
* this operation must be called.
|
||||||
|
*
|
||||||
|
* @param habitGroups the list of habits that have been modified.
|
||||||
|
*/
|
||||||
|
abstract fun update(habitGroups: List<HabitGroup>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the list that a certain habit has been modified.
|
||||||
|
*
|
||||||
|
* See [.update] for more details.
|
||||||
|
*
|
||||||
|
* @param habitGroup the habit that has been modified.
|
||||||
|
*/
|
||||||
|
fun update(habitGroup: HabitGroup) {
|
||||||
|
update(listOf(habitGroup))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the list of habits to the given writer, in CSV format. There is
|
||||||
|
* one line for each habit, containing the fields name, description,
|
||||||
|
* frequency numerator, frequency denominator and color. The color is
|
||||||
|
* written in HTML format (#000000).
|
||||||
|
*
|
||||||
|
* @param out the writer that will receive the result
|
||||||
|
* @throws IOException if write operations fail
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun writeCSV(out: Writer) {
|
||||||
|
val header = arrayOf(
|
||||||
|
"Position",
|
||||||
|
"Name",
|
||||||
|
"Question",
|
||||||
|
"Description",
|
||||||
|
"NumRepetitions",
|
||||||
|
"Interval",
|
||||||
|
"Color"
|
||||||
|
)
|
||||||
|
val csv = CSVWriter(out)
|
||||||
|
csv.writeNext(header, false)
|
||||||
|
for (habit in this) {
|
||||||
|
val cols = arrayOf(
|
||||||
|
String.format("%03d", indexOf(habit) + 1),
|
||||||
|
habit.name,
|
||||||
|
habit.question,
|
||||||
|
habit.description,
|
||||||
|
habit.color.toCsvColor()
|
||||||
|
)
|
||||||
|
csv.writeNext(cols, false)
|
||||||
|
}
|
||||||
|
csv.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue