mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 21:18:51 -06:00
Implement listing sub habits
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.activities.habits.list.views
|
package org.isoron.uhabits.activities.habits.list.views
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView.Adapter
|
import androidx.recyclerview.widget.RecyclerView.Adapter
|
||||||
import org.isoron.uhabits.activities.habits.list.MAX_CHECKMARK_COUNT
|
import org.isoron.uhabits.activities.habits.list.MAX_CHECKMARK_COUNT
|
||||||
@@ -74,9 +75,14 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
return cache.hasNoHabitGroup()
|
return cache.hasNoHabitGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasNoSubHabits(): Boolean {
|
||||||
|
return cache.hasNoSubHabits()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets all items as not selected.
|
* Sets all items as not selected.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
override fun clearSelection() {
|
override fun clearSelection() {
|
||||||
selectedHabits.clear()
|
selectedHabits.clear()
|
||||||
selectedHabitGroups.clear()
|
selectedHabitGroups.clear()
|
||||||
@@ -116,7 +122,7 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
val uuidString = getItemUUID(position)
|
val uuidString = cache.getUUIDByPosition(position)
|
||||||
return if (uuidString != null) {
|
return if (uuidString != null) {
|
||||||
val formattedUUIDString = formatUUID(uuidString)
|
val formattedUUIDString = formatUUID(uuidString)
|
||||||
val uuid = UUID.fromString(formattedUUIDString)
|
val uuid = UUID.fromString(formattedUUIDString)
|
||||||
@@ -126,18 +132,6 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getItemUUID(position: Int): String? {
|
|
||||||
val h = cache.getHabitByPosition(position)
|
|
||||||
val hgr = cache.getHabitGroupByPosition(position)
|
|
||||||
return if (h != null) {
|
|
||||||
h.uuid!!
|
|
||||||
} else if (hgr != null) {
|
|
||||||
hgr.uuid!!
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatUUID(uuidString: String): String {
|
private fun formatUUID(uuidString: String): String {
|
||||||
return uuidString.substring(0, 8) + "-" +
|
return uuidString.substring(0, 8) + "-" +
|
||||||
uuidString.substring(8, 12) + "-" +
|
uuidString.substring(8, 12) + "-" +
|
||||||
@@ -207,7 +201,7 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
|
|
||||||
// function to override getItemViewType and return the type of the view. The view can either be a HabitCardView or a HabitGroupCardView
|
// function to override getItemViewType and return the type of the view. The view can either be a HabitCardView or a HabitGroupCardView
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return if (position < cache.habitCount) {
|
return if (cache.getHabitByPosition(position) != null) {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
@@ -322,6 +316,7 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param position position of the item to be toggled
|
* @param position position of the item to be toggled
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun toggleSelection(position: Int) {
|
fun toggleSelection(position: Int) {
|
||||||
val h = cache.getHabitByPosition(position)
|
val h = cache.getHabitByPosition(position)
|
||||||
val hgr = cache.getHabitGroupByPosition(position)
|
val hgr = cache.getHabitGroupByPosition(position)
|
||||||
|
|||||||
@@ -136,11 +136,10 @@ class HabitCardView(
|
|||||||
init {
|
init {
|
||||||
scoreRing = RingView(context).apply {
|
scoreRing = RingView(context).apply {
|
||||||
val thickness = dp(3f)
|
val thickness = dp(3f)
|
||||||
val rightMargin = dp(8f).toInt()
|
val margin = dp(8f).toInt()
|
||||||
val ringSize = dp(15f).toInt()
|
val ringSize = dp(15f).toInt()
|
||||||
val leftMargin = if (habit?.isSubHabit() == true) dp(30f).toInt() else dp(8f).toInt()
|
|
||||||
layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply {
|
layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply {
|
||||||
setMargins(leftMargin, 0, rightMargin, 0)
|
setMargins(margin, 0, margin, 0)
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
}
|
}
|
||||||
setThickness(thickness)
|
setThickness(thickness)
|
||||||
@@ -268,6 +267,16 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
scoreRing.apply {
|
scoreRing.apply {
|
||||||
setColor(c)
|
setColor(c)
|
||||||
|
if (h.isSubHabit()) {
|
||||||
|
val rightMargin = dp(8f).toInt()
|
||||||
|
val ringSize = dp(15f).toInt()
|
||||||
|
val leftMargin =
|
||||||
|
if (habit?.isSubHabit() == true) dp(30f).toInt() else dp(8f).toInt()
|
||||||
|
layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply {
|
||||||
|
setMargins(leftMargin, 0, rightMargin, 0)
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
checkmarkPanel.apply {
|
checkmarkPanel.apply {
|
||||||
color = c
|
color = c
|
||||||
|
|||||||
@@ -67,6 +67,20 @@ abstract class HabitList : Iterable<Habit> {
|
|||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
abstract fun add(habit: Habit)
|
abstract fun add(habit: Habit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new habit in the list at the given position.
|
||||||
|
*
|
||||||
|
* 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 habit the habit to be inserted
|
||||||
|
* @throws IllegalArgumentException if the habit is already on the list.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
abstract fun add(position: Int, habit: Habit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the habit with specified id.
|
* Returns the habit with specified id.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -195,7 +195,11 @@ class MemoryHabitGroupList : HabitGroupList {
|
|||||||
private fun loadFromParent() {
|
private fun loadFromParent() {
|
||||||
checkNotNull(parent)
|
checkNotNull(parent)
|
||||||
list.clear()
|
list.clear()
|
||||||
for (h in parent!!) if (filter.matches(h)) list.add(h)
|
for (h in parent!!) {
|
||||||
|
if (filter.matches(h)) {
|
||||||
|
list.add(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
resort()
|
resort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,17 @@ class MemoryHabitList : HabitList {
|
|||||||
resort()
|
resort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
override fun add(position: Int, habit: Habit) {
|
||||||
|
throwIfHasParent()
|
||||||
|
require(!list.contains(habit)) { "habit already added" }
|
||||||
|
val id = habit.id
|
||||||
|
if (id != null && getById(id) != null) throw RuntimeException("duplicate id")
|
||||||
|
if (id == null) habit.id = list.size.toLong()
|
||||||
|
list.add(position, habit)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun getById(id: Long): Habit? {
|
override fun getById(id: Long): Habit? {
|
||||||
for (h in list) {
|
for (h in list) {
|
||||||
|
|||||||
@@ -64,6 +64,19 @@ class SQLiteHabitList @Inject constructor(private val modelFactory: ModelFactory
|
|||||||
observable.notifyListeners()
|
observable.notifyListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun add(position: Int, habit: Habit) {
|
||||||
|
loadRecords()
|
||||||
|
habit.position = size()
|
||||||
|
val record = HabitRecord()
|
||||||
|
record.copyFrom(habit)
|
||||||
|
repository.save(record)
|
||||||
|
habit.id = record.id
|
||||||
|
(habit.originalEntries as SQLiteEntryList).habitId = record.id
|
||||||
|
list.add(position, habit)
|
||||||
|
observable.notifyListeners()
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun getById(id: Long): Habit? {
|
override fun getById(id: Long): Habit? {
|
||||||
loadRecords()
|
loadRecords()
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
@AppScope
|
@AppScope
|
||||||
class HabitCardListCache @Inject constructor(
|
class HabitCardListCache @Inject constructor(
|
||||||
private val allHabits: HabitList,
|
private val habits: HabitList,
|
||||||
private val allHabitGroups: HabitGroupList,
|
private val habitGroups: HabitGroupList,
|
||||||
private val commandRunner: CommandRunner,
|
private val commandRunner: CommandRunner,
|
||||||
taskRunner: TaskRunner,
|
taskRunner: TaskRunner,
|
||||||
logging: Logging
|
logging: Logging
|
||||||
@@ -68,6 +68,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
private val data: CacheData
|
private val data: CacheData
|
||||||
private var filteredHabits: HabitList
|
private var filteredHabits: HabitList
|
||||||
private var filteredHabitGroups: HabitGroupList
|
private var filteredHabitGroups: HabitGroupList
|
||||||
|
private var filteredSubHabits: MutableList<HabitList>
|
||||||
private val taskRunner: TaskRunner
|
private val taskRunner: TaskRunner
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -87,12 +88,17 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun hasNoHabit(): Boolean {
|
fun hasNoHabit(): Boolean {
|
||||||
return allHabits.isEmpty
|
return habits.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun hasNoHabitGroup(): Boolean {
|
fun hasNoHabitGroup(): Boolean {
|
||||||
return allHabitGroups.isEmpty
|
return habitGroups.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun hasNoSubHabits(): Boolean {
|
||||||
|
return habitGroups.all { it.habitList.isEmpty }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,11 +109,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getHabitByPosition(position: Int): Habit? {
|
fun getHabitByPosition(position: Int): Habit? {
|
||||||
return if (position < 0 || position >= data.habits.size) {
|
return data.positionToHabit[position]
|
||||||
null
|
|
||||||
} else {
|
|
||||||
data.habits[position]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,16 +120,21 @@ class HabitCardListCache @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getHabitGroupByPosition(position: Int): HabitGroup? {
|
fun getHabitGroupByPosition(position: Int): HabitGroup? {
|
||||||
return if (position < data.habits.size || position >= data.habits.size + data.habitGroups.size) {
|
return data.positionToHabitGroup[position]
|
||||||
null
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun getUUIDByPosition(position: Int): String? {
|
||||||
|
return if (data.positionTypes[position] == STANDALONE_HABIT || data.positionTypes[position] == SUB_HABIT) {
|
||||||
|
data.positionToHabit[position]!!.uuid
|
||||||
} else {
|
} else {
|
||||||
data.habitGroups[position - data.habits.size]
|
data.positionToHabitGroup[position]!!.uuid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
val itemCount: Int
|
val itemCount: Int
|
||||||
get() = habitCount + habitGroupCount
|
get() = habitCount + habitGroupCount + subHabitCount
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
val habitCount: Int
|
val habitCount: Int
|
||||||
@@ -137,15 +144,20 @@ class HabitCardListCache @Inject constructor(
|
|||||||
val habitGroupCount: Int
|
val habitGroupCount: Int
|
||||||
get() = data.habitGroups.size
|
get() = data.habitGroups.size
|
||||||
|
|
||||||
|
@get:Synchronized
|
||||||
|
val subHabitCount: Int
|
||||||
|
get() = data.subHabits.sumOf { it.size() }
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
@set:Synchronized
|
@set:Synchronized
|
||||||
var primaryOrder: Order
|
var primaryOrder: Order
|
||||||
get() = filteredHabits.primaryOrder
|
get() = filteredHabits.primaryOrder
|
||||||
set(order) {
|
set(order) {
|
||||||
allHabits.primaryOrder = order
|
habits.primaryOrder = order
|
||||||
|
habitGroups.primaryOrder = order
|
||||||
filteredHabits.primaryOrder = order
|
filteredHabits.primaryOrder = order
|
||||||
allHabitGroups.primaryOrder = order
|
|
||||||
filteredHabitGroups.primaryOrder = order
|
filteredHabitGroups.primaryOrder = order
|
||||||
|
filteredSubHabits.forEach { it.primaryOrder = order }
|
||||||
refreshAllHabits()
|
refreshAllHabits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,16 +166,17 @@ class HabitCardListCache @Inject constructor(
|
|||||||
var secondaryOrder: Order
|
var secondaryOrder: Order
|
||||||
get() = filteredHabits.secondaryOrder
|
get() = filteredHabits.secondaryOrder
|
||||||
set(order) {
|
set(order) {
|
||||||
allHabits.secondaryOrder = order
|
habits.secondaryOrder = order
|
||||||
|
habitGroups.secondaryOrder = order
|
||||||
filteredHabits.secondaryOrder = order
|
filteredHabits.secondaryOrder = order
|
||||||
allHabitGroups.secondaryOrder = order
|
|
||||||
filteredHabitGroups.secondaryOrder = order
|
filteredHabitGroups.secondaryOrder = order
|
||||||
|
filteredSubHabits.forEach { it.secondaryOrder = order }
|
||||||
refreshAllHabits()
|
refreshAllHabits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getScore(habitUUID: String): Double {
|
fun getScore(uuid: String): Double {
|
||||||
return data.scores[habitUUID]!!
|
return data.scores[uuid]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -201,42 +214,68 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun remove(uuid: String) {
|
fun remove(uuid: String) {
|
||||||
val h = data.uuidToHabit[uuid]
|
val type = data.positionTypes[data.uuidToPosition[uuid]!!]
|
||||||
if (h != null) {
|
if (type == STANDALONE_HABIT) {
|
||||||
val position = data.habits.indexOf(h)
|
val h = data.uuidToHabit[uuid]
|
||||||
data.habits.removeAt(position)
|
if (h != null) {
|
||||||
data.uuidToHabit.remove(uuid)
|
val position = data.habits.indexOf(h)
|
||||||
data.checkmarks.remove(uuid)
|
data.habits.removeAt(position)
|
||||||
data.notes.remove(uuid)
|
data.checkmarks.remove(uuid)
|
||||||
data.scores.remove(uuid)
|
data.notes.remove(uuid)
|
||||||
listener.onItemRemoved(position)
|
data.scores.remove(uuid)
|
||||||
} else {
|
data.decrementPositions(position + 1, data.positionTypes.size)
|
||||||
|
listener.onItemRemoved(position)
|
||||||
|
}
|
||||||
|
} else if (type == SUB_HABIT) {
|
||||||
|
val h = data.uuidToHabit[uuid]
|
||||||
|
if (h != null) {
|
||||||
|
val position = data.uuidToPosition[uuid]!!
|
||||||
|
val hgrUUID = h.parentUUID
|
||||||
|
val hgr = data.uuidToHabitGroup[hgrUUID]
|
||||||
|
val hgrIdx = data.habitGroups.indexOf(hgr)
|
||||||
|
data.subHabits[hgrIdx].remove(h)
|
||||||
|
data.checkmarks.remove(uuid)
|
||||||
|
data.notes.remove(uuid)
|
||||||
|
data.scores.remove(uuid)
|
||||||
|
data.decrementPositions(position + 1, data.positionTypes.size)
|
||||||
|
listener.onItemRemoved(position)
|
||||||
|
}
|
||||||
|
} else if (type == HABIT_GROUP) {
|
||||||
val hgr = data.uuidToHabitGroup[uuid]
|
val hgr = data.uuidToHabitGroup[uuid]
|
||||||
if (hgr != null) {
|
if (hgr != null) {
|
||||||
val position = data.habitGroups.indexOf(hgr)
|
val position = data.uuidToPosition[uuid]!!
|
||||||
data.habitGroups.removeAt(position)
|
val hgrIdx = data.habitGroups.indexOf(hgr)
|
||||||
data.uuidToHabitGroup.remove(uuid)
|
|
||||||
listener.onItemRemoved(position + data.habits.size)
|
for (habit in data.subHabits[hgrIdx].reversed()) {
|
||||||
|
data.checkmarks.remove(habit.uuid)
|
||||||
|
data.notes.remove(habit.uuid)
|
||||||
|
data.scores.remove(habit.uuid)
|
||||||
|
listener.onItemRemoved(data.uuidToPosition[habit.uuid]!!)
|
||||||
|
}
|
||||||
|
data.subHabits.removeAt(hgrIdx)
|
||||||
|
data.habitGroups.removeAt(hgrIdx)
|
||||||
|
data.scores.remove(hgr.uuid)
|
||||||
|
data.rebuildPositions()
|
||||||
|
listener.onItemRemoved(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun reorder(from: Int, to: Int) {
|
fun reorder(from: Int, to: Int) {
|
||||||
if (data.habits.size in (from + 1)..to || data.habits.size in (to + 1)..from) {
|
if (from == to) return
|
||||||
logger.error("reorder: from and to are in different sections")
|
val uuid = if (data.positionTypes[from] == STANDALONE_HABIT) {
|
||||||
return
|
data.positionToHabit[from]!!.uuid
|
||||||
}
|
|
||||||
if (from < data.habits.size) {
|
|
||||||
val fromHabit = data.habits[from]
|
|
||||||
data.habits.removeAt(from)
|
|
||||||
data.habits.add(to, fromHabit)
|
|
||||||
} else {
|
} else {
|
||||||
val fromHabitGroup = data.habitGroups[from]
|
data.positionToHabitGroup[from]!!.uuid
|
||||||
data.habitGroups.removeAt(from - data.habits.size)
|
}
|
||||||
data.habitGroups.add(to - data.habits.size, fromHabitGroup)
|
if (data.positionTypes[from] == STANDALONE_HABIT) {
|
||||||
|
val habit = data.positionToHabit[from]!!
|
||||||
|
data.performMove(habit, from, to)
|
||||||
|
} else if (data.positionTypes[from] == HABIT_GROUP) {
|
||||||
|
val habitGroup = data.positionToHabitGroup[from]!!
|
||||||
|
data.performMove(habitGroup, from, to)
|
||||||
}
|
}
|
||||||
listener.onItemMoved(from, to)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -246,8 +285,11 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun setFilter(matcher: HabitMatcher) {
|
fun setFilter(matcher: HabitMatcher) {
|
||||||
filteredHabits = allHabits.getFiltered(matcher)
|
filteredHabits = habits.getFiltered(matcher)
|
||||||
filteredHabitGroups = allHabitGroups.getFiltered(matcher)
|
filteredHabitGroups = habitGroups.getFiltered(matcher)
|
||||||
|
for (idx in filteredSubHabits.indices) {
|
||||||
|
filteredSubHabits[idx] = filteredSubHabits[idx].getFiltered(matcher)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -272,6 +314,11 @@ class HabitCardListCache @Inject constructor(
|
|||||||
val uuidToHabitGroup: HashMap<String?, HabitGroup> = HashMap()
|
val uuidToHabitGroup: HashMap<String?, HabitGroup> = HashMap()
|
||||||
val habits: MutableList<Habit>
|
val habits: MutableList<Habit>
|
||||||
val habitGroups: MutableList<HabitGroup>
|
val habitGroups: MutableList<HabitGroup>
|
||||||
|
val subHabits: MutableList<HabitList>
|
||||||
|
val uuidToPosition: HashMap<String?, Int>
|
||||||
|
val positionTypes: MutableList<Int>
|
||||||
|
val positionToHabit: HashMap<Int, Habit>
|
||||||
|
val positionToHabitGroup: HashMap<Int, HabitGroup>
|
||||||
val checkmarks: HashMap<String?, IntArray>
|
val checkmarks: HashMap<String?, IntArray>
|
||||||
val scores: HashMap<String?, Double>
|
val scores: HashMap<String?, Double>
|
||||||
val notes: HashMap<String?, Array<String>>
|
val notes: HashMap<String?, Array<String>>
|
||||||
@@ -327,22 +374,199 @@ class HabitCardListCache @Inject constructor(
|
|||||||
for (h in filteredHabits) {
|
for (h in filteredHabits) {
|
||||||
if (h.uuid == null) continue
|
if (h.uuid == null) continue
|
||||||
habits.add(h)
|
habits.add(h)
|
||||||
uuidToHabit[h.uuid] = h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (hgr in filteredHabitGroups) {
|
for (hgr in filteredHabitGroups) {
|
||||||
if (hgr.uuid == null) continue
|
if (hgr.uuid == null) continue
|
||||||
habitGroups.add(hgr)
|
habitGroups.add(hgr)
|
||||||
uuidToHabitGroup[hgr.uuid] = hgr
|
val habitList = hgr.habitList
|
||||||
|
subHabits.add(habitList)
|
||||||
|
|
||||||
|
for (h in habitList) {
|
||||||
|
if (h.uuid == null) continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun rebuildPositions() {
|
||||||
|
positionToHabit.clear()
|
||||||
|
positionToHabitGroup.clear()
|
||||||
|
uuidToPosition.clear()
|
||||||
|
positionTypes.clear()
|
||||||
|
var position = 0
|
||||||
|
for (h in habits) {
|
||||||
|
uuidToHabit[h.uuid] = h
|
||||||
|
uuidToPosition[h.uuid] = position
|
||||||
|
positionToHabit[position] = h
|
||||||
|
positionTypes.add(STANDALONE_HABIT)
|
||||||
|
position++
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((idx, hgr) in habitGroups.withIndex()) {
|
||||||
|
uuidToHabitGroup[hgr.uuid] = hgr
|
||||||
|
uuidToPosition[hgr.uuid] = position
|
||||||
|
positionToHabitGroup[position] = hgr
|
||||||
|
positionTypes.add(HABIT_GROUP)
|
||||||
|
val habitList = subHabits[idx]
|
||||||
|
position++
|
||||||
|
|
||||||
|
for (h in habitList) {
|
||||||
|
uuidToHabit[h.uuid] = h
|
||||||
|
uuidToPosition[h.uuid] = position
|
||||||
|
positionToHabit[position] = h
|
||||||
|
positionTypes.add(SUB_HABIT)
|
||||||
|
position++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun isValidInsert(habit: Habit, position: Int): Boolean {
|
||||||
|
if (habit.parentUUID == null) {
|
||||||
|
return position <= habits.size
|
||||||
|
} else {
|
||||||
|
val parent = uuidToHabitGroup[habit.parentUUID]
|
||||||
|
if (parent == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val parentPosition = uuidToPosition[habit.parentUUID]!!
|
||||||
|
val parentIndex = habitGroups.indexOf(parent)
|
||||||
|
val nextGroup = habitGroups.getOrNull(parentIndex + 1)
|
||||||
|
val nextGroupPosition = uuidToPosition[nextGroup?.uuid]
|
||||||
|
return (position > parentPosition && position <= positionTypes.size) && (nextGroupPosition == null || position <= nextGroupPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun isValidInsert(habitGroup: HabitGroup, position: Int): Boolean {
|
||||||
|
return (position == positionTypes.size) || (positionTypes[position] == HABIT_GROUP)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun incrementPositions(from: Int, to: Int) {
|
||||||
|
for (pos in positionToHabit.keys.sortedByDescending { it }) {
|
||||||
|
if (pos in from..to) {
|
||||||
|
positionToHabit[pos + 1] = positionToHabit[pos]!!
|
||||||
|
positionToHabit.remove(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (pos in positionToHabitGroup.keys.sortedByDescending { it }) {
|
||||||
|
if (pos in from..to) {
|
||||||
|
positionToHabitGroup[pos + 1] = positionToHabitGroup[pos]!!
|
||||||
|
positionToHabitGroup.remove(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((key, pos) in uuidToPosition.entries) {
|
||||||
|
if (pos in from..to) {
|
||||||
|
uuidToPosition[key] = pos + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun decrementPositions(fromPosition: Int, toPosition: Int) {
|
||||||
|
positionTypes.removeAt(fromPosition)
|
||||||
|
for (pos in positionToHabit.keys.sortedBy { it }) {
|
||||||
|
if (pos in fromPosition..toPosition) {
|
||||||
|
positionToHabit[pos - 1] = positionToHabit[pos]!!
|
||||||
|
positionToHabit.remove(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (pos in positionToHabitGroup.keys.sortedBy { it }) {
|
||||||
|
if (pos in fromPosition..toPosition) {
|
||||||
|
positionToHabitGroup[pos - 1] = positionToHabitGroup[pos]!!
|
||||||
|
positionToHabitGroup.remove(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((key, pos) in uuidToPosition.entries) {
|
||||||
|
if (pos in fromPosition..toPosition) {
|
||||||
|
uuidToPosition[key] = pos - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun performMove(
|
||||||
|
habit: Habit,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int
|
||||||
|
) {
|
||||||
|
val type = positionTypes[fromPosition]
|
||||||
|
if (type == HABIT_GROUP) return
|
||||||
|
|
||||||
|
// Workaround for https://github.com/iSoron/uhabits/issues/968
|
||||||
|
val checkedToPosition = if (toPosition > positionTypes.size) {
|
||||||
|
logger.error("performMove: $toPosition for habit is strictly higher than ${habits.size}")
|
||||||
|
positionTypes.size
|
||||||
|
} else {
|
||||||
|
toPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
val verifyPosition = if (fromPosition > checkedToPosition) checkedToPosition else checkedToPosition + 1
|
||||||
|
if (!isValidInsert(habit, verifyPosition)) return
|
||||||
|
|
||||||
|
if (type == STANDALONE_HABIT) {
|
||||||
|
habits.removeAt(fromPosition)
|
||||||
|
if (fromPosition < checkedToPosition) {
|
||||||
|
decrementPositions(fromPosition + 1, checkedToPosition)
|
||||||
|
} else {
|
||||||
|
incrementPositions(toPosition, fromPosition - 1)
|
||||||
|
}
|
||||||
|
habits.add(checkedToPosition, habit)
|
||||||
|
positionTypes.add(checkedToPosition, STANDALONE_HABIT)
|
||||||
|
} else {
|
||||||
|
val hgr = uuidToHabitGroup[habit.parentUUID]
|
||||||
|
val hgrIdx = habitGroups.indexOf(hgr)
|
||||||
|
val h = positionToHabit[fromPosition]!!
|
||||||
|
subHabits[hgrIdx].remove(h)
|
||||||
|
if (fromPosition < checkedToPosition) {
|
||||||
|
decrementPositions(fromPosition + 1, checkedToPosition)
|
||||||
|
} else {
|
||||||
|
incrementPositions(toPosition, fromPosition - 1)
|
||||||
|
}
|
||||||
|
subHabits[hgrIdx].add(checkedToPosition - uuidToPosition[hgr!!.uuid]!! - 1, habit)
|
||||||
|
positionTypes.add(checkedToPosition, SUB_HABIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
positionToHabit[checkedToPosition] = habit
|
||||||
|
uuidToPosition[habit.uuid] = checkedToPosition
|
||||||
|
listener.onItemMoved(fromPosition, checkedToPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun performMove(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int
|
||||||
|
) {
|
||||||
|
if (positionTypes[fromPosition] != HABIT_GROUP) return
|
||||||
|
if (!isValidInsert(habitGroup, toPosition)) return
|
||||||
|
val fromIdx = habitGroups.indexOf(habitGroup)
|
||||||
|
val habitList = subHabits[fromIdx]
|
||||||
|
val toIdx = habitGroups.indexOf(positionToHabitGroup[toPosition]) - (if (fromPosition < toPosition) 1 else 0)
|
||||||
|
|
||||||
|
habitGroups.removeAt(fromIdx)
|
||||||
|
subHabits.removeAt(fromIdx)
|
||||||
|
|
||||||
|
habitGroups.add(toIdx, habitGroup)
|
||||||
|
subHabits.add(toIdx, habitList)
|
||||||
|
|
||||||
|
rebuildPositions()
|
||||||
|
listener.onItemMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new CacheData without any content.
|
* Creates a new CacheData without any content.
|
||||||
*/
|
*/
|
||||||
init {
|
init {
|
||||||
habits = LinkedList()
|
habits = LinkedList()
|
||||||
habitGroups = LinkedList()
|
habitGroups = LinkedList()
|
||||||
|
subHabits = LinkedList()
|
||||||
|
positionTypes = LinkedList()
|
||||||
|
uuidToPosition = HashMap()
|
||||||
|
positionToHabit = HashMap()
|
||||||
|
positionToHabitGroup = HashMap()
|
||||||
checkmarks = HashMap()
|
checkmarks = HashMap()
|
||||||
scores = HashMap()
|
scores = HashMap()
|
||||||
notes = HashMap()
|
notes = HashMap()
|
||||||
@@ -374,35 +598,35 @@ class HabitCardListCache @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
override fun doInBackground() {
|
override fun doInBackground() {
|
||||||
newData.fetchHabits()
|
newData.fetchHabits()
|
||||||
|
newData.rebuildPositions()
|
||||||
newData.copyScoresFrom(data)
|
newData.copyScoresFrom(data)
|
||||||
newData.copyCheckmarksFrom(data)
|
newData.copyCheckmarksFrom(data)
|
||||||
newData.copyNoteIndicatorsFrom(data)
|
newData.copyNoteIndicatorsFrom(data)
|
||||||
val today = getTodayWithOffset()
|
val today = getTodayWithOffset()
|
||||||
val dateFrom = today.minus(checkmarkCount - 1)
|
val dateFrom = today.minus(checkmarkCount - 1)
|
||||||
if (runner != null) runner!!.publishProgress(this, -1)
|
if (runner != null) runner!!.publishProgress(this, -1)
|
||||||
for (position in newData.habits.indices) {
|
for ((position, type) in newData.positionTypes.withIndex()) {
|
||||||
if (isCancelled) return
|
if (isCancelled) return
|
||||||
val habit = newData.habits[position]
|
if (type == STANDALONE_HABIT || type == SUB_HABIT) {
|
||||||
if (targetUUID != null && targetUUID != habit.uuid) continue
|
val habit = newData.positionToHabit[position]!!
|
||||||
newData.scores[habit.uuid] = habit.scores[today].value
|
if (targetUUID != null && targetUUID != habit.uuid) continue
|
||||||
val list: MutableList<Int> = ArrayList()
|
newData.scores[habit.uuid] = habit.scores[today].value
|
||||||
val notes: MutableList<String> = ArrayList()
|
val list: MutableList<Int> = ArrayList()
|
||||||
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
val notes: MutableList<String> = ArrayList()
|
||||||
list.add(value)
|
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
||||||
notes.add(note)
|
list.add(value)
|
||||||
|
notes.add(note)
|
||||||
|
}
|
||||||
|
val entries = list.toTypedArray()
|
||||||
|
newData.checkmarks[habit.uuid] = ArrayUtils.toPrimitive(entries)
|
||||||
|
newData.notes[habit.uuid] = notes.toTypedArray()
|
||||||
|
runner!!.publishProgress(this, position)
|
||||||
|
} else if (type == HABIT_GROUP) {
|
||||||
|
val habitGroup = newData.positionToHabitGroup[position]!!
|
||||||
|
if (targetUUID != null && targetUUID != habitGroup.uuid) continue
|
||||||
|
newData.scores[habitGroup.uuid] = habitGroup.scores[today].value
|
||||||
|
runner!!.publishProgress(this, position)
|
||||||
}
|
}
|
||||||
val entries = list.toTypedArray()
|
|
||||||
newData.checkmarks[habit.uuid] = ArrayUtils.toPrimitive(entries)
|
|
||||||
newData.notes[habit.uuid] = notes.toTypedArray()
|
|
||||||
runner!!.publishProgress(this, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (position in newData.habitGroups.indices) {
|
|
||||||
if (isCancelled) return
|
|
||||||
val hgr = newData.habitGroups[position]
|
|
||||||
if (targetUUID != null && targetUUID != hgr.uuid) continue
|
|
||||||
newData.scores[hgr.uuid] = hgr.scores[today].value
|
|
||||||
runner!!.publishProgress(this, position + newData.habits.size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,8 +648,21 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun performInsert(habit: Habit, position: Int) {
|
private fun performInsert(habit: Habit, position: Int) {
|
||||||
|
if (!data.isValidInsert(habit, position)) return
|
||||||
val uuid = habit.uuid
|
val uuid = habit.uuid
|
||||||
data.habits.add(position, habit)
|
if (habit.parentUUID == null) {
|
||||||
|
data.habits.add(position, habit)
|
||||||
|
data.positionTypes.add(position, STANDALONE_HABIT)
|
||||||
|
} else {
|
||||||
|
// val parent = data.uuidToHabitGroup[habit.parentUUID]
|
||||||
|
// val parentIdx = data.habitGroups.indexOf(parent)
|
||||||
|
// val parentPosition = data.uuidToPosition[habit.parentUUID]!!
|
||||||
|
// data.subHabits[parentIdx].add(position - parentPosition - 1, habit)
|
||||||
|
data.positionTypes.add(position, SUB_HABIT)
|
||||||
|
}
|
||||||
|
data.incrementPositions(position, data.positionTypes.size - 1)
|
||||||
|
data.positionToHabit[position] = habit
|
||||||
|
data.uuidToPosition[uuid] = position
|
||||||
data.uuidToHabit[uuid] = habit
|
data.uuidToHabit[uuid] = habit
|
||||||
data.scores[uuid] = newData.scores[uuid]!!
|
data.scores[uuid] = newData.scores[uuid]!!
|
||||||
data.checkmarks[uuid] = newData.checkmarks[uuid]!!
|
data.checkmarks[uuid] = newData.checkmarks[uuid]!!
|
||||||
@@ -435,62 +672,23 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun performInsert(habitGroup: HabitGroup, position: Int) {
|
private fun performInsert(habitGroup: HabitGroup, position: Int) {
|
||||||
val newPosition = if (position < data.habits.size) {
|
if (!data.isValidInsert(habitGroup, position)) return
|
||||||
data.habits.size
|
|
||||||
} else {
|
|
||||||
position
|
|
||||||
}
|
|
||||||
val uuid = habitGroup.uuid
|
val uuid = habitGroup.uuid
|
||||||
data.habitGroups.add(newPosition - data.habits.size, habitGroup)
|
val prevIdx = newData.habitGroups.indexOf(habitGroup)
|
||||||
data.uuidToHabitGroup[uuid] = habitGroup
|
val habitList = newData.subHabits[prevIdx]
|
||||||
|
var idx = data.habitGroups.indexOf(data.positionToHabitGroup[position])
|
||||||
|
if (idx < 0) idx = data.habitGroups.size
|
||||||
|
|
||||||
|
data.habitGroups.add(idx, habitGroup)
|
||||||
|
data.subHabits.add(prevIdx, habitList)
|
||||||
data.scores[uuid] = newData.scores[uuid]!!
|
data.scores[uuid] = newData.scores[uuid]!!
|
||||||
listener.onItemInserted(newPosition)
|
for (h in habitList) {
|
||||||
}
|
data.scores[h.uuid] = newData.scores[h.uuid]!!
|
||||||
|
data.checkmarks[h.uuid] = newData.checkmarks[h.uuid]!!
|
||||||
@Synchronized
|
data.notes[h.uuid] = newData.notes[h.uuid]!!
|
||||||
private fun performMove(
|
|
||||||
habit: Habit,
|
|
||||||
fromPosition: Int,
|
|
||||||
toPosition: Int
|
|
||||||
) {
|
|
||||||
data.habits.removeAt(fromPosition)
|
|
||||||
|
|
||||||
// Workaround for https://github.com/iSoron/uhabits/issues/968
|
|
||||||
val checkedToPosition = if (toPosition > data.habits.size) {
|
|
||||||
logger.error("performMove: $toPosition for habit is strictly higher than ${data.habits.size}")
|
|
||||||
data.habits.size
|
|
||||||
} else {
|
|
||||||
toPosition
|
|
||||||
}
|
}
|
||||||
|
data.rebuildPositions()
|
||||||
data.habits.add(checkedToPosition, habit)
|
listener.onItemInserted(position)
|
||||||
listener.onItemMoved(fromPosition, checkedToPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun performMove(
|
|
||||||
habitGroup: HabitGroup,
|
|
||||||
fromPosition: Int,
|
|
||||||
toPosition: Int
|
|
||||||
) {
|
|
||||||
if (fromPosition < data.habits.size || fromPosition > data.habits.size + data.habitGroups.size) {
|
|
||||||
logger.error("performMove: $fromPosition for habit group is out of bounds")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data.habitGroups.removeAt(fromPosition - data.habits.size)
|
|
||||||
|
|
||||||
// Workaround for https://github.com/iSoron/uhabits/issues/968
|
|
||||||
val checkedToPosition = if (toPosition < data.habits.size) {
|
|
||||||
logger.error("performMove: $toPosition for habit group is strictly lower than ${data.habits.size}")
|
|
||||||
data.habits.size
|
|
||||||
} else if (toPosition > data.habits.size + data.habitGroups.size) {
|
|
||||||
logger.error("performMove: $toPosition for habit group is strictly higher than ${data.habits.size + data.habitGroups.size}")
|
|
||||||
data.habits.size + data.habitGroups.size
|
|
||||||
} else {
|
|
||||||
toPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
data.habitGroups.add(checkedToPosition - data.habits.size, habitGroup)
|
|
||||||
listener.onItemMoved(fromPosition, checkedToPosition)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -500,7 +698,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
val newScore = newData.scores[uuid]!!
|
val newScore = newData.scores[uuid]!!
|
||||||
if (oldScore != newScore) unchanged = false
|
if (oldScore != newScore) unchanged = false
|
||||||
|
|
||||||
if (position < data.habits.size) {
|
if (data.positionTypes[position] != HABIT_GROUP) {
|
||||||
val oldCheckmarks = data.checkmarks[uuid]
|
val oldCheckmarks = data.checkmarks[uuid]
|
||||||
val newCheckmarks = newData.checkmarks[uuid]!!
|
val newCheckmarks = newData.checkmarks[uuid]!!
|
||||||
val oldNoteIndicators = data.notes[uuid]
|
val oldNoteIndicators = data.notes[uuid]
|
||||||
@@ -519,38 +717,45 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun processPosition(currentPosition: Int) {
|
private fun processPosition(currentPosition: Int) {
|
||||||
if (currentPosition < newData.habits.size) {
|
val type = newData.positionTypes[currentPosition]
|
||||||
val habit = newData.habits[currentPosition]
|
|
||||||
val uuid = habit.uuid
|
if (type == STANDALONE_HABIT || type == SUB_HABIT) {
|
||||||
val prevPosition = data.habits.indexOf(habit)
|
val habit = newData.positionToHabit[currentPosition]!!
|
||||||
if (prevPosition < 0) {
|
val uuid = habit.uuid ?: throw NullPointerException()
|
||||||
performInsert(habit, currentPosition)
|
val prevPosition = data.uuidToPosition[uuid] ?: -1
|
||||||
|
val newPosition = if (type == STANDALONE_HABIT) {
|
||||||
|
currentPosition
|
||||||
} else {
|
} else {
|
||||||
if (prevPosition != currentPosition) {
|
val hgr = data.uuidToHabitGroup[habit.parentUUID]
|
||||||
performMove(
|
val hgrIdx = data.habitGroups.indexOf(hgr)
|
||||||
|
newData.subHabits[hgrIdx].indexOf(habit) + data.uuidToPosition[hgr!!.uuid]!! + 1
|
||||||
|
}
|
||||||
|
if (prevPosition < 0) {
|
||||||
|
performInsert(habit, newPosition)
|
||||||
|
} else {
|
||||||
|
if (prevPosition != newPosition) {
|
||||||
|
data.performMove(
|
||||||
habit,
|
habit,
|
||||||
prevPosition,
|
prevPosition,
|
||||||
currentPosition
|
newPosition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (uuid == null) throw NullPointerException()
|
|
||||||
performUpdate(uuid, currentPosition)
|
performUpdate(uuid, currentPosition)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (type == HABIT_GROUP) {
|
||||||
val habitGroup = newData.habitGroups[currentPosition - data.habits.size]
|
val habitGroup = newData.positionToHabitGroup[currentPosition]!!
|
||||||
val uuid = habitGroup.uuid
|
val uuid = habitGroup.uuid ?: throw NullPointerException()
|
||||||
val prevPosition = data.habitGroups.indexOf(habitGroup) + data.habits.size
|
val prevPosition = data.uuidToPosition[uuid] ?: -1
|
||||||
if (prevPosition < data.habits.size) {
|
if (prevPosition < 0) {
|
||||||
performInsert(habitGroup, currentPosition)
|
performInsert(habitGroup, currentPosition)
|
||||||
} else {
|
} else {
|
||||||
if (prevPosition != currentPosition) {
|
if (prevPosition != currentPosition) {
|
||||||
performMove(
|
data.performMove(
|
||||||
habitGroup,
|
habitGroup,
|
||||||
prevPosition,
|
prevPosition,
|
||||||
currentPosition
|
currentPosition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (uuid == null) throw NullPointerException()
|
|
||||||
performUpdate(uuid, currentPosition)
|
performUpdate(uuid, currentPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,27 +763,29 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun processRemovedHabits() {
|
private fun processRemovedHabits() {
|
||||||
val before: Set<String?> = data.uuidToHabit.keys
|
val before: Set<String?> = (data.uuidToHabit.keys).union(data.uuidToHabitGroup.keys)
|
||||||
val after: Set<String?> = newData.uuidToHabit.keys
|
val after: Set<String?> = (newData.uuidToHabit.keys).union(newData.uuidToHabitGroup.keys)
|
||||||
val removed: MutableSet<String?> = TreeSet(before)
|
val removed: MutableSet<String?> = TreeSet(before)
|
||||||
removed.removeAll(after)
|
removed.removeAll(after)
|
||||||
for (uuid in removed) remove(uuid!!)
|
for (uuid in removed.sortedBy { uuid -> data.uuidToPosition[uuid] }) remove(uuid!!)
|
||||||
processRemovedHabitGroups()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun processRemovedHabitGroups() {
|
|
||||||
val before: Set<String?> = data.uuidToHabitGroup.keys
|
|
||||||
val after: Set<String?> = newData.uuidToHabitGroup.keys
|
|
||||||
val removed: MutableSet<String?> = TreeSet(before)
|
|
||||||
removed.removeAll(after)
|
|
||||||
for (uuid in removed) remove(uuid!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val STANDALONE_HABIT = 0
|
||||||
|
const val HABIT_GROUP = 1
|
||||||
|
const val SUB_HABIT = 2
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filteredHabits = allHabits
|
filteredHabits = habits
|
||||||
filteredHabitGroups = allHabitGroups
|
filteredHabitGroups = habitGroups
|
||||||
|
filteredSubHabits = LinkedList()
|
||||||
|
for (hgr in habitGroups) {
|
||||||
|
val subList = hgr.habitList
|
||||||
|
filteredSubHabits.add(subList)
|
||||||
|
}
|
||||||
|
|
||||||
this.taskRunner = taskRunner
|
this.taskRunner = taskRunner
|
||||||
listener = object : Listener {}
|
listener = object : Listener {}
|
||||||
data = CacheData()
|
data = CacheData()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.isoron.uhabits.core.database.Database
|
|||||||
import org.isoron.uhabits.core.database.DatabaseOpener
|
import org.isoron.uhabits.core.database.DatabaseOpener
|
||||||
import org.isoron.uhabits.core.database.JdbcDatabase
|
import org.isoron.uhabits.core.database.JdbcDatabase
|
||||||
import org.isoron.uhabits.core.database.MigrationHelper
|
import org.isoron.uhabits.core.database.MigrationHelper
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroupList
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.ModelFactory
|
import org.isoron.uhabits.core.models.ModelFactory
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
@@ -52,6 +53,7 @@ import java.sql.SQLException
|
|||||||
@RunWith(MockitoJUnitRunner::class)
|
@RunWith(MockitoJUnitRunner::class)
|
||||||
open class BaseUnitTest {
|
open class BaseUnitTest {
|
||||||
protected open lateinit var habitList: HabitList
|
protected open lateinit var habitList: HabitList
|
||||||
|
protected open lateinit var habitGroupList: HabitGroupList
|
||||||
protected lateinit var fixtures: HabitFixtures
|
protected lateinit var fixtures: HabitFixtures
|
||||||
protected lateinit var modelFactory: ModelFactory
|
protected lateinit var modelFactory: ModelFactory
|
||||||
protected lateinit var taskRunner: SingleThreadTaskRunner
|
protected lateinit var taskRunner: SingleThreadTaskRunner
|
||||||
@@ -80,6 +82,7 @@ open class BaseUnitTest {
|
|||||||
setStartDayOffset(0, 0)
|
setStartDayOffset(0, 0)
|
||||||
val memoryModelFactory = MemoryModelFactory()
|
val memoryModelFactory = MemoryModelFactory()
|
||||||
habitList = spy(memoryModelFactory.buildHabitList())
|
habitList = spy(memoryModelFactory.buildHabitList())
|
||||||
|
habitGroupList = spy(memoryModelFactory.buildHabitGroupList())
|
||||||
fixtures = HabitFixtures(memoryModelFactory, habitList)
|
fixtures = HabitFixtures(memoryModelFactory, habitList)
|
||||||
modelFactory = memoryModelFactory
|
modelFactory = memoryModelFactory
|
||||||
taskRunner = SingleThreadTaskRunner()
|
taskRunner = SingleThreadTaskRunner()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class HabitCardListCacheTest : BaseUnitTest() {
|
|||||||
for (i in 0..9) {
|
for (i in 0..9) {
|
||||||
if (i == 3) habitList.add(fixtures.createLongHabit()) else habitList.add(fixtures.createShortHabit())
|
if (i == 3) habitList.add(fixtures.createLongHabit()) else habitList.add(fixtures.createShortHabit())
|
||||||
}
|
}
|
||||||
cache = HabitCardListCache(habitList, commandRunner, taskRunner, mock())
|
cache = HabitCardListCache(habitList, habitGroupList, commandRunner, taskRunner, mock())
|
||||||
cache.setCheckmarkCount(10)
|
cache.setCheckmarkCount(10)
|
||||||
cache.refreshAllHabits()
|
cache.refreshAllHabits()
|
||||||
cache.onAttached()
|
cache.onAttached()
|
||||||
@@ -82,8 +82,8 @@ class HabitCardListCacheTest : BaseUnitTest() {
|
|||||||
val h = habitList.getByPosition(3)
|
val h = habitList.getByPosition(3)
|
||||||
val score = h.scores[today].value
|
val score = h.scores[today].value
|
||||||
assertThat(cache.getHabitByPosition(3), equalTo(h))
|
assertThat(cache.getHabitByPosition(3), equalTo(h))
|
||||||
assertThat(cache.getScore(h.id!!), equalTo(score))
|
assertThat(cache.getScore(h.uuid!!), equalTo(score))
|
||||||
val actualCheckmarks = cache.getCheckmarks(h.id!!)
|
val actualCheckmarks = cache.getCheckmarks(h.uuid!!)
|
||||||
|
|
||||||
val expectedCheckmarks = h
|
val expectedCheckmarks = h
|
||||||
.computedEntries
|
.computedEntries
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ class ListHabitsSelectionMenuBehaviorTest : BaseUnitTest() {
|
|||||||
habitList.add(habit3)
|
habitList.add(habit3)
|
||||||
behavior = ListHabitsSelectionMenuBehavior(
|
behavior = ListHabitsSelectionMenuBehavior(
|
||||||
habitList,
|
habitList,
|
||||||
|
habitGroupList,
|
||||||
screen,
|
screen,
|
||||||
adapter,
|
adapter,
|
||||||
commandRunner
|
commandRunner
|
||||||
|
|||||||
Reference in New Issue
Block a user