mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 21:18:51 -06:00
Can show habit group without interaction / scrolling
This commit is contained in:
@@ -53,7 +53,7 @@ class HabitCardViewTest : BaseViewTest() {
|
|||||||
.getByInterval(today.minus(300), today)
|
.getByInterval(today.minus(300), today)
|
||||||
.map { it.value }.toIntArray()
|
.map { it.value }.toIntArray()
|
||||||
|
|
||||||
view = component.getHabitCardViewFactory().create().apply {
|
view = component.getHabitCardViewFactory().createHabitCard().apply {
|
||||||
habit = habit1
|
habit = habit1
|
||||||
values = entries
|
values = entries
|
||||||
score = habit1.scores[today].value
|
score = habit1.scores[today].value
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class ListHabitsRootView @Inject constructor(
|
|||||||
|
|
||||||
private fun updateEmptyView() {
|
private fun updateEmptyView() {
|
||||||
if (listAdapter.itemCount == 0) {
|
if (listAdapter.itemCount == 0) {
|
||||||
if (listAdapter.hasNoHabit()) {
|
if (listAdapter.hasNoHabit() && listAdapter.hasNoHabitGroup()) {
|
||||||
llEmpty.showEmpty()
|
llEmpty.showEmpty()
|
||||||
} else {
|
} else {
|
||||||
llEmpty.showDone()
|
llEmpty.showDone()
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class ListHabitsSelectionMenu @Inject constructor(
|
|||||||
itemArchive.isVisible = behavior.canArchive()
|
itemArchive.isVisible = behavior.canArchive()
|
||||||
itemUnarchive.isVisible = behavior.canUnarchive()
|
itemUnarchive.isVisible = behavior.canUnarchive()
|
||||||
itemNotify.isVisible = prefs.isDeveloper
|
itemNotify.isVisible = prefs.isDeveloper
|
||||||
activeActionMode?.title = listAdapter.selected.size.toString()
|
activeActionMode?.title = listAdapter.selectedHabits.size.toString()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
@@ -117,7 +117,7 @@ class ListHabitsSelectionMenu @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_notify -> {
|
R.id.action_notify -> {
|
||||||
for (h in listAdapter.selected)
|
for (h in listAdapter.selectedHabits)
|
||||||
notificationTray.show(h, DateUtils.getToday(), 0)
|
notificationTray.show(h, DateUtils.getToday(), 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.isoron.uhabits.activities.habits.list.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.MeasureSpec.EXACTLY
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.utils.getFontAwesome
|
||||||
|
import org.isoron.uhabits.utils.sp
|
||||||
|
import org.isoron.uhabits.utils.sres
|
||||||
|
import org.isoron.uhabits.utils.toMeasureSpec
|
||||||
|
|
||||||
|
class AddButtonView(
|
||||||
|
context: Context
|
||||||
|
) : View(context),
|
||||||
|
View.OnClickListener {
|
||||||
|
|
||||||
|
var onEdit: () -> Unit = { }
|
||||||
|
|
||||||
|
private var drawer = Drawer()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setOnClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
onEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
drawer.draw(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
val height = resources.getDimensionPixelSize(R.dimen.checkmarkHeight)
|
||||||
|
val width = resources.getDimensionPixelSize(R.dimen.checkmarkWidth)
|
||||||
|
super.onMeasure(
|
||||||
|
width.toMeasureSpec(EXACTLY),
|
||||||
|
height.toMeasureSpec(EXACTLY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Drawer {
|
||||||
|
private val rect = RectF()
|
||||||
|
private val highContrastColor = sres.getColor(R.attr.contrast100)
|
||||||
|
|
||||||
|
private val paint = TextPaint().apply {
|
||||||
|
typeface = getFontAwesome()
|
||||||
|
isAntiAlias = true
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
fun draw(canvas: Canvas) {
|
||||||
|
paint.color = highContrastColor
|
||||||
|
val id = R.string.fa_plus
|
||||||
|
paint.textSize = sp(12.0f)
|
||||||
|
paint.strokeWidth = 0f
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
|
||||||
|
val label = resources.getString(id)
|
||||||
|
val em = paint.measureText("m")
|
||||||
|
|
||||||
|
rect.set(0f, 0f, width.toFloat(), height.toFloat())
|
||||||
|
rect.offset(0f, 0.4f * em)
|
||||||
|
canvas.drawText(label, rect.centerX(), rect.centerY(), paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,9 +19,10 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views
|
package org.isoron.uhabits.activities.habits.list.views
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.HabitMatcher
|
import org.isoron.uhabits.core.models.HabitMatcher
|
||||||
import org.isoron.uhabits.core.models.ModelObservable
|
import org.isoron.uhabits.core.models.ModelObservable
|
||||||
@@ -32,6 +33,7 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBeh
|
|||||||
import org.isoron.uhabits.core.utils.MidnightTimer
|
import org.isoron.uhabits.core.utils.MidnightTimer
|
||||||
import org.isoron.uhabits.inject.ActivityScope
|
import org.isoron.uhabits.inject.ActivityScope
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,14 +48,16 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
private val cache: HabitCardListCache,
|
private val cache: HabitCardListCache,
|
||||||
private val preferences: Preferences,
|
private val preferences: Preferences,
|
||||||
private val midnightTimer: MidnightTimer
|
private val midnightTimer: MidnightTimer
|
||||||
) : RecyclerView.Adapter<HabitCardViewHolder?>(),
|
) : Adapter<HabitCardViewHolder?>(),
|
||||||
HabitCardListCache.Listener,
|
HabitCardListCache.Listener,
|
||||||
MidnightTimer.MidnightListener,
|
MidnightTimer.MidnightListener,
|
||||||
ListHabitsMenuBehavior.Adapter,
|
ListHabitsMenuBehavior.Adapter,
|
||||||
ListHabitsSelectionMenuBehavior.Adapter {
|
ListHabitsSelectionMenuBehavior.Adapter {
|
||||||
val observable: ModelObservable = ModelObservable()
|
val observable: ModelObservable = ModelObservable()
|
||||||
private var listView: HabitCardListView? = null
|
private var listView: HabitCardListView? = null
|
||||||
val selected: LinkedList<Habit> = LinkedList()
|
val selectedHabits: LinkedList<Habit> = LinkedList()
|
||||||
|
val selectedHabitGroups: LinkedList<HabitGroup> = LinkedList()
|
||||||
|
|
||||||
override fun atMidnight() {
|
override fun atMidnight() {
|
||||||
cache.refreshAllHabits()
|
cache.refreshAllHabits()
|
||||||
}
|
}
|
||||||
@@ -66,17 +70,25 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
return cache.hasNoHabit()
|
return cache.hasNoHabit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasNoHabitGroup(): Boolean {
|
||||||
|
return cache.hasNoHabitGroup()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets all items as not selected.
|
* Sets all items as not selected.
|
||||||
*/
|
*/
|
||||||
override fun clearSelection() {
|
override fun clearSelection() {
|
||||||
selected.clear()
|
selectedHabits.clear()
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
observable.notifyListeners()
|
observable.notifyListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSelected(): List<Habit> {
|
override fun getSelected(): List<Habit> {
|
||||||
return ArrayList(selected)
|
return ArrayList(selectedHabits)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSelectedHabitGroups(): List<HabitGroup> {
|
||||||
|
return ArrayList(selectedHabitGroups)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,11 +103,38 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return cache.habitCount
|
return cache.itemCount
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return getItem(position)!!.id!!
|
val uuidString = getItemUUID(position)
|
||||||
|
return if (uuidString != null) {
|
||||||
|
val formattedUUIDString = formatUUID(uuidString)
|
||||||
|
val uuid = UUID.fromString(formattedUUIDString)
|
||||||
|
uuid.mostSignificantBits and Long.MAX_VALUE
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return uuidString.substring(0, 8) + "-" +
|
||||||
|
uuidString.substring(8, 12) + "-" +
|
||||||
|
uuidString.substring(12, 16) + "-" +
|
||||||
|
uuidString.substring(16, 20) + "-" +
|
||||||
|
uuidString.substring(20, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +143,7 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
* @return true if selection is empty, false otherwise
|
* @return true if selection is empty, false otherwise
|
||||||
*/
|
*/
|
||||||
val isSelectionEmpty: Boolean
|
val isSelectionEmpty: Boolean
|
||||||
get() = selected.isEmpty()
|
get() = selectedHabits.isEmpty() && selectedHabitGroups.isEmpty()
|
||||||
val isSortable: Boolean
|
val isSortable: Boolean
|
||||||
get() = cache.primaryOrder == HabitList.Order.BY_POSITION
|
get() = cache.primaryOrder == HabitList.Order.BY_POSITION
|
||||||
|
|
||||||
@@ -122,11 +161,18 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
if (listView == null) return
|
if (listView == null) return
|
||||||
val habit = cache.getHabitByPosition(position)
|
val habit = cache.getHabitByPosition(position)
|
||||||
val score = cache.getScore(habit!!.id!!)
|
if (habit != null) {
|
||||||
val checkmarks = cache.getCheckmarks(habit.id!!)
|
val score = cache.getScore(habit.uuid!!)
|
||||||
val notes = cache.getNotes(habit.id!!)
|
val checkmarks = cache.getCheckmarks(habit.uuid!!)
|
||||||
val selected = selected.contains(habit)
|
val notes = cache.getNotes(habit.uuid!!)
|
||||||
listView!!.bindCardView(holder, habit, score, checkmarks, notes, selected)
|
val selected = selectedHabits.contains(habit)
|
||||||
|
listView!!.bindCardView(holder, habit, score, checkmarks, notes, selected)
|
||||||
|
} else {
|
||||||
|
val habitGroup = cache.getHabitGroupByPosition(position)
|
||||||
|
val score = cache.getScore(habitGroup!!.uuid!!)
|
||||||
|
val selected = selectedHabitGroups.contains(habitGroup)
|
||||||
|
listView!!.bindGroupCardView(holder, habitGroup, score, selected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: HabitCardViewHolder) {
|
override fun onViewAttachedToWindow(holder: HabitCardViewHolder) {
|
||||||
@@ -141,8 +187,22 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
): HabitCardViewHolder {
|
): HabitCardViewHolder {
|
||||||
val view = listView!!.createHabitCardView()
|
if (viewType == 0) {
|
||||||
return HabitCardViewHolder(view)
|
val view = listView!!.createHabitCardView()
|
||||||
|
return HabitCardViewHolder(view, null)
|
||||||
|
} else {
|
||||||
|
val view = listView!!.createHabitGroupCardView()
|
||||||
|
return HabitCardViewHolder(null, view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return if (position < cache.habitCount) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +250,11 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
* @param selected list of habits to be removed
|
* @param selected list of habits to be removed
|
||||||
*/
|
*/
|
||||||
override fun performRemove(selected: List<Habit>) {
|
override fun performRemove(selected: List<Habit>) {
|
||||||
for (habit in selected) cache.remove(habit.id!!)
|
for (habit in selected) cache.remove(habit.uuid!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun performRemoveHabitGroup(selected: List<HabitGroup>) {
|
||||||
|
for (hgr in selected) cache.remove(hgr.uuid!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -250,10 +314,17 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
* @param position position of the item to be toggled
|
* @param position position of the item to be toggled
|
||||||
*/
|
*/
|
||||||
fun toggleSelection(position: Int) {
|
fun toggleSelection(position: Int) {
|
||||||
val h = getItem(position) ?: return
|
val h = cache.getHabitByPosition(position)
|
||||||
val k = selected.indexOf(h)
|
val hgr = cache.getHabitGroupByPosition(position)
|
||||||
if (k < 0) selected.add(h) else selected.remove(h)
|
if (h != null) {
|
||||||
notifyDataSetChanged()
|
val k = selectedHabits.indexOf(h)
|
||||||
|
if (k < 0) selectedHabits.add(h) else selectedHabits.remove(h)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
} else if (hgr != null) {
|
||||||
|
val k = selectedHabitGroups.indexOf(hgr)
|
||||||
|
if (k < 0) selectedHabitGroups.add(hgr) else selectedHabitGroups.remove(hgr)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import dagger.Lazy
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.views.BundleSavedState
|
import org.isoron.uhabits.activities.common.views.BundleSavedState
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -79,7 +80,11 @@ class HabitCardListView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createHabitCardView(): HabitCardView {
|
fun createHabitCardView(): HabitCardView {
|
||||||
return cardViewFactory.create()
|
return cardViewFactory.createHabitCard()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createHabitGroupCardView(): HabitGroupCardView {
|
||||||
|
return cardViewFactory.createHabitGroupCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bindCardView(
|
fun bindCardView(
|
||||||
@@ -110,8 +115,28 @@ class HabitCardListView(
|
|||||||
return cardView
|
return cardView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bindGroupCardView(
|
||||||
|
holder: HabitCardViewHolder,
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
score: Double,
|
||||||
|
selected: Boolean
|
||||||
|
): View {
|
||||||
|
val cardView = holder.itemView as HabitGroupCardView
|
||||||
|
cardView.habitGroup = habitGroup
|
||||||
|
cardView.isSelected = selected
|
||||||
|
cardView.score = score
|
||||||
|
|
||||||
|
val detector = GestureDetector(context, CardViewGestureDetector(holder))
|
||||||
|
cardView.setOnTouchListener { _, ev ->
|
||||||
|
detector.onTouchEvent(ev)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardView
|
||||||
|
}
|
||||||
|
|
||||||
fun attachCardView(holder: HabitCardViewHolder) {
|
fun attachCardView(holder: HabitCardViewHolder) {
|
||||||
(holder.itemView as HabitCardView).dataOffset = dataOffset
|
(holder.itemView as? HabitCardView)?.dataOffset = dataOffset
|
||||||
attachedHolders.add(holder)
|
attachedHolders.add(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.isoron.platform.gui.toInt
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.common.views.RingView
|
import org.isoron.uhabits.activities.common.views.RingView
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.ModelObservable
|
import org.isoron.uhabits.core.models.ModelObservable
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||||
@@ -55,7 +56,8 @@ class HabitCardViewFactory
|
|||||||
private val numberPanelFactory: NumberPanelViewFactory,
|
private val numberPanelFactory: NumberPanelViewFactory,
|
||||||
private val behavior: ListHabitsBehavior
|
private val behavior: ListHabitsBehavior
|
||||||
) {
|
) {
|
||||||
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
fun createHabitCard() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
|
||||||
|
fun createHabitGroupCard() = HabitGroupCardView(context, behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
class HabitCardView(
|
class HabitCardView(
|
||||||
@@ -285,6 +287,32 @@ class HabitCardView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun copyAttributesFrom(hgr: HabitGroup) {
|
||||||
|
fun getActiveColor(habitGroup: HabitGroup): Int {
|
||||||
|
return when (habitGroup.isArchived) {
|
||||||
|
true -> sres.getColor(R.attr.contrast60)
|
||||||
|
false -> currentTheme().color(habitGroup.color).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val c = getActiveColor(hgr)
|
||||||
|
label.apply {
|
||||||
|
text = hgr.name
|
||||||
|
setTextColor(c)
|
||||||
|
}
|
||||||
|
scoreRing.apply {
|
||||||
|
setColor(c)
|
||||||
|
}
|
||||||
|
checkmarkPanel.apply {
|
||||||
|
color = c
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
numberPanel.apply {
|
||||||
|
color = c
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun triggerRipple(x: Float, y: Float) {
|
private fun triggerRipple(x: Float, y: Float) {
|
||||||
val background = innerFrame.background
|
val background = innerFrame.background
|
||||||
background.setHotspot(x, y)
|
background.setHotspot(x, y)
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ package org.isoron.uhabits.activities.habits.list.views
|
|||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
class HabitCardViewHolder(itemView: HabitCardView) : RecyclerView.ViewHolder(itemView)
|
class HabitCardViewHolder(itemView1: HabitCardView?, itemView2: HabitGroupCardView?) : RecyclerView.ViewHolder(itemView1 ?: itemView2!!)
|
||||||
|
|||||||
@@ -23,15 +23,6 @@ import org.isoron.uhabits.inject.ActivityContext
|
|||||||
import org.isoron.uhabits.utils.currentTheme
|
import org.isoron.uhabits.utils.currentTheme
|
||||||
import org.isoron.uhabits.utils.dp
|
import org.isoron.uhabits.utils.dp
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class HabitGroupCardViewFactory
|
|
||||||
@Inject constructor(
|
|
||||||
@ActivityContext val context: Context,
|
|
||||||
private val behavior: ListHabitsBehavior
|
|
||||||
) {
|
|
||||||
fun create() = HabitGroupCardView(context, behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
class HabitGroupCardView(
|
class HabitGroupCardView(
|
||||||
@ActivityContext context: Context,
|
@ActivityContext context: Context,
|
||||||
@@ -56,6 +47,7 @@ class HabitGroupCardView(
|
|||||||
scoreRing.setPrecision(1.0f / 16)
|
scoreRing.setPrecision(1.0f / 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addButtonView: AddButtonView
|
||||||
private var innerFrame: LinearLayout
|
private var innerFrame: LinearLayout
|
||||||
private var label: TextView
|
private var label: TextView
|
||||||
private var scoreRing: RingView
|
private var scoreRing: RingView
|
||||||
@@ -83,6 +75,8 @@ class HabitGroupCardView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addButtonView = AddButtonView(context)
|
||||||
|
|
||||||
innerFrame = LinearLayout(context).apply {
|
innerFrame = LinearLayout(context).apply {
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
orientation = LinearLayout.HORIZONTAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
@@ -91,6 +85,7 @@ class HabitGroupCardView(
|
|||||||
|
|
||||||
addView(scoreRing)
|
addView(scoreRing)
|
||||||
addView(label)
|
addView(label)
|
||||||
|
addView(addButtonView)
|
||||||
|
|
||||||
setOnTouchListener { v, event ->
|
setOnTouchListener { v, event ->
|
||||||
v.background.setHotspot(event.x, event.y)
|
v.background.setHotspot(event.x, event.y)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
class HabitGroupCardViewHolder(itemView: HabitGroupCardView) : RecyclerView.ViewHolder(itemView)
|
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
<string translatable="false" name="fa_arrow_circle_down"></string>
|
<string translatable="false" name="fa_arrow_circle_down"></string>
|
||||||
<string translatable="false" name="fa_check"></string>
|
<string translatable="false" name="fa_check"></string>
|
||||||
<string translatable="false" name="fa_times"></string>
|
<string translatable="false" name="fa_times"></string>
|
||||||
|
<string translatable="false" name="fa_plus"></string>
|
||||||
<string translatable="false" name="fa_skipped"></string>
|
<string translatable="false" name="fa_skipped"></string>
|
||||||
<string translatable="false" name="fa_bell_o"></string>
|
<string translatable="false" name="fa_bell_o"></string>
|
||||||
<string translatable="false" name="fa_calendar"></string>
|
<string translatable="false" name="fa_calendar"></string>
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class ScoreList {
|
|||||||
var current = to
|
var current = to
|
||||||
while (current >= from) {
|
while (current >= from) {
|
||||||
val habitScores = habitList.map { it.scores[current].value }
|
val habitScores = habitList.map { it.scores[current].value }
|
||||||
val averageScore = habitScores.average()
|
val averageScore = if (habitScores.isNotEmpty()) habitScores.average() else 0.0
|
||||||
map[current] = Score(current, averageScore)
|
map[current] = Score(current, averageScore)
|
||||||
current = current.minus(1)
|
current = current.minus(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ import org.isoron.uhabits.core.commands.CommandRunner
|
|||||||
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
||||||
import org.isoron.uhabits.core.io.Logging
|
import org.isoron.uhabits.core.io.Logging
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
|
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.HabitList.Order
|
import org.isoron.uhabits.core.models.HabitList.Order
|
||||||
import org.isoron.uhabits.core.models.HabitMatcher
|
import org.isoron.uhabits.core.models.HabitMatcher
|
||||||
import org.isoron.uhabits.core.tasks.Task
|
import org.isoron.uhabits.core.tasks.Task
|
||||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -54,6 +54,7 @@ import javax.inject.Inject
|
|||||||
@AppScope
|
@AppScope
|
||||||
class HabitCardListCache @Inject constructor(
|
class HabitCardListCache @Inject constructor(
|
||||||
private val allHabits: HabitList,
|
private val allHabits: HabitList,
|
||||||
|
private val allHabitGroups: HabitGroupList,
|
||||||
private val commandRunner: CommandRunner,
|
private val commandRunner: CommandRunner,
|
||||||
taskRunner: TaskRunner,
|
taskRunner: TaskRunner,
|
||||||
logging: Logging
|
logging: Logging
|
||||||
@@ -66,6 +67,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
private var listener: Listener
|
private var listener: Listener
|
||||||
private val data: CacheData
|
private val data: CacheData
|
||||||
private var filteredHabits: HabitList
|
private var filteredHabits: HabitList
|
||||||
|
private var filteredHabitGroups: HabitGroupList
|
||||||
private val taskRunner: TaskRunner
|
private val taskRunner: TaskRunner
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -74,13 +76,13 @@ class HabitCardListCache @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getCheckmarks(habitId: Long): IntArray {
|
fun getCheckmarks(habitUUID: String): IntArray {
|
||||||
return data.checkmarks[habitId]!!
|
return data.checkmarks[habitUUID]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getNotes(habitId: Long): Array<String> {
|
fun getNotes(habitUUID: String): Array<String> {
|
||||||
return data.notes[habitId]!!
|
return data.notes[habitUUID]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -88,21 +90,53 @@ class HabitCardListCache @Inject constructor(
|
|||||||
return allHabits.isEmpty
|
return allHabits.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun hasNoHabitGroup(): Boolean {
|
||||||
|
return allHabitGroups.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the habits that occupies a certain position on the list.
|
* Returns the habits that occupies a certain position on the list.
|
||||||
*
|
*
|
||||||
* @param position the position of the habit
|
* @param position the position of the list of habits and groups
|
||||||
* @return the habit at given position or null if position is invalid
|
* @return the habit at given position or null if position is invalid
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getHabitByPosition(position: Int): Habit? {
|
fun getHabitByPosition(position: Int): Habit? {
|
||||||
return if (position < 0 || position >= data.habits.size) null else data.habits[position]
|
return if (position < 0 || position >= data.habits.size) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
data.habits[position]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit groups that occupies a certain position on the list.
|
||||||
|
*
|
||||||
|
* @param position the position of the list of habits and groups
|
||||||
|
* @return the habit group at given position or null if position is invalid
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun getHabitGroupByPosition(position: Int): HabitGroup? {
|
||||||
|
return if (position < data.habits.size || position >= data.habits.size + data.habitGroups.size) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
data.habitGroups[position - data.habits.size]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Synchronized
|
||||||
|
val itemCount: Int
|
||||||
|
get() = habitCount + habitGroupCount
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
val habitCount: Int
|
val habitCount: Int
|
||||||
get() = data.habits.size
|
get() = data.habits.size
|
||||||
|
|
||||||
|
@get:Synchronized
|
||||||
|
val habitGroupCount: Int
|
||||||
|
get() = data.habitGroups.size
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
@set:Synchronized
|
@set:Synchronized
|
||||||
var primaryOrder: Order
|
var primaryOrder: Order
|
||||||
@@ -110,6 +144,8 @@ class HabitCardListCache @Inject constructor(
|
|||||||
set(order) {
|
set(order) {
|
||||||
allHabits.primaryOrder = order
|
allHabits.primaryOrder = order
|
||||||
filteredHabits.primaryOrder = order
|
filteredHabits.primaryOrder = order
|
||||||
|
allHabitGroups.primaryOrder = order
|
||||||
|
filteredHabitGroups.primaryOrder = order
|
||||||
refreshAllHabits()
|
refreshAllHabits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +156,14 @@ class HabitCardListCache @Inject constructor(
|
|||||||
set(order) {
|
set(order) {
|
||||||
allHabits.secondaryOrder = order
|
allHabits.secondaryOrder = order
|
||||||
filteredHabits.secondaryOrder = order
|
filteredHabits.secondaryOrder = order
|
||||||
|
allHabitGroups.secondaryOrder = order
|
||||||
|
filteredHabitGroups.secondaryOrder = order
|
||||||
refreshAllHabits()
|
refreshAllHabits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getScore(habitId: Long): Double {
|
fun getScore(habitUUID: String): Double {
|
||||||
return data.scores[habitId]!!
|
return data.scores[habitUUID]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -137,7 +175,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
override fun onCommandFinished(command: Command) {
|
override fun onCommandFinished(command: Command) {
|
||||||
if (command is CreateRepetitionCommand) {
|
if (command is CreateRepetitionCommand) {
|
||||||
command.habit.id?.let { refreshHabit(it) }
|
command.habit.uuid?.let { refreshHabit(it) }
|
||||||
} else {
|
} else {
|
||||||
refreshAllHabits()
|
refreshAllHabits()
|
||||||
}
|
}
|
||||||
@@ -157,27 +195,47 @@ class HabitCardListCache @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun refreshHabit(id: Long) {
|
fun refreshHabit(uuid: String) {
|
||||||
taskRunner.execute(RefreshTask(id))
|
taskRunner.execute(RefreshTask(uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun remove(id: Long) {
|
fun remove(uuid: String) {
|
||||||
val h = data.idToHabit[id] ?: return
|
val h = data.uuidToHabit[uuid]
|
||||||
val position = data.habits.indexOf(h)
|
if (h != null) {
|
||||||
data.habits.removeAt(position)
|
val position = data.habits.indexOf(h)
|
||||||
data.idToHabit.remove(id)
|
data.habits.removeAt(position)
|
||||||
data.checkmarks.remove(id)
|
data.uuidToHabit.remove(uuid)
|
||||||
data.notes.remove(id)
|
data.checkmarks.remove(uuid)
|
||||||
data.scores.remove(id)
|
data.notes.remove(uuid)
|
||||||
listener.onItemRemoved(position)
|
data.scores.remove(uuid)
|
||||||
|
listener.onItemRemoved(position)
|
||||||
|
} else {
|
||||||
|
val hgr = data.uuidToHabitGroup[uuid]
|
||||||
|
if (hgr != null) {
|
||||||
|
val position = data.habitGroups.indexOf(hgr)
|
||||||
|
data.habitGroups.removeAt(position)
|
||||||
|
data.uuidToHabitGroup.remove(uuid)
|
||||||
|
listener.onItemRemoved(position + data.habits.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun reorder(from: Int, to: Int) {
|
fun reorder(from: Int, to: Int) {
|
||||||
val fromHabit = data.habits[from]
|
if (data.habits.size in (from + 1)..to || data.habits.size in (to + 1)..from) {
|
||||||
data.habits.removeAt(from)
|
logger.error("reorder: from and to are in different sections")
|
||||||
data.habits.add(to, fromHabit)
|
return
|
||||||
|
}
|
||||||
|
if (from < data.habits.size) {
|
||||||
|
val fromHabit = data.habits[from]
|
||||||
|
data.habits.removeAt(from)
|
||||||
|
data.habits.add(to, fromHabit)
|
||||||
|
} else {
|
||||||
|
val fromHabitGroup = data.habitGroups[from]
|
||||||
|
data.habitGroups.removeAt(from - data.habits.size)
|
||||||
|
data.habitGroups.add(to - data.habits.size, fromHabitGroup)
|
||||||
|
}
|
||||||
listener.onItemMoved(from, to)
|
listener.onItemMoved(from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +247,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun setFilter(matcher: HabitMatcher) {
|
fun setFilter(matcher: HabitMatcher) {
|
||||||
filteredHabits = allHabits.getFiltered(matcher)
|
filteredHabits = allHabits.getFiltered(matcher)
|
||||||
|
filteredHabitGroups = allHabitGroups.getFiltered(matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -209,21 +268,23 @@ class HabitCardListCache @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class CacheData {
|
private inner class CacheData {
|
||||||
val idToHabit: HashMap<Long?, Habit> = HashMap()
|
val uuidToHabit: HashMap<String?, Habit> = HashMap()
|
||||||
|
val uuidToHabitGroup: HashMap<String?, HabitGroup> = HashMap()
|
||||||
val habits: MutableList<Habit>
|
val habits: MutableList<Habit>
|
||||||
val checkmarks: HashMap<Long?, IntArray>
|
val habitGroups: MutableList<HabitGroup>
|
||||||
val scores: HashMap<Long?, Double>
|
val checkmarks: HashMap<String?, IntArray>
|
||||||
val notes: HashMap<Long?, Array<String>>
|
val scores: HashMap<String?, Double>
|
||||||
|
val notes: HashMap<String?, Array<String>>
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun copyCheckmarksFrom(oldData: CacheData) {
|
fun copyCheckmarksFrom(oldData: CacheData) {
|
||||||
val empty = IntArray(checkmarkCount)
|
val empty = IntArray(checkmarkCount)
|
||||||
for (id in idToHabit.keys) {
|
for (uuid in uuidToHabit.keys) {
|
||||||
if (oldData.checkmarks.containsKey(id)) {
|
if (oldData.checkmarks.containsKey(uuid)) {
|
||||||
checkmarks[id] =
|
checkmarks[uuid] =
|
||||||
oldData.checkmarks[id]!!
|
oldData.checkmarks[uuid]!!
|
||||||
} else {
|
} else {
|
||||||
checkmarks[id] = empty
|
checkmarks[uuid] = empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,24 +292,32 @@ class HabitCardListCache @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun copyNoteIndicatorsFrom(oldData: CacheData) {
|
fun copyNoteIndicatorsFrom(oldData: CacheData) {
|
||||||
val empty = (0..checkmarkCount).map { "" }.toTypedArray()
|
val empty = (0..checkmarkCount).map { "" }.toTypedArray()
|
||||||
for (id in idToHabit.keys) {
|
for (uuid in uuidToHabit.keys) {
|
||||||
if (oldData.notes.containsKey(id)) {
|
if (oldData.notes.containsKey(uuid)) {
|
||||||
notes[id] =
|
notes[uuid] =
|
||||||
oldData.notes[id]!!
|
oldData.notes[uuid]!!
|
||||||
} else {
|
} else {
|
||||||
notes[id] = empty
|
notes[uuid] = empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun copyScoresFrom(oldData: CacheData) {
|
fun copyScoresFrom(oldData: CacheData) {
|
||||||
for (id in idToHabit.keys) {
|
for (uuid in uuidToHabit.keys) {
|
||||||
if (oldData.scores.containsKey(id)) {
|
if (oldData.scores.containsKey(uuid)) {
|
||||||
scores[id] =
|
scores[uuid] =
|
||||||
oldData.scores[id]!!
|
oldData.scores[uuid]!!
|
||||||
} else {
|
} else {
|
||||||
scores[id] = 0.0
|
scores[uuid] = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uuid in uuidToHabitGroup.keys) {
|
||||||
|
if (oldData.scores.containsKey(uuid)) {
|
||||||
|
scores[uuid] =
|
||||||
|
oldData.scores[uuid]!!
|
||||||
|
} else {
|
||||||
|
scores[uuid] = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,9 +325,15 @@ class HabitCardListCache @Inject constructor(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun fetchHabits() {
|
fun fetchHabits() {
|
||||||
for (h in filteredHabits) {
|
for (h in filteredHabits) {
|
||||||
if (h.id == null) continue
|
if (h.uuid == null) continue
|
||||||
habits.add(h)
|
habits.add(h)
|
||||||
idToHabit[h.id] = h
|
uuidToHabit[h.uuid] = h
|
||||||
|
}
|
||||||
|
|
||||||
|
for (hgr in filteredHabitGroups) {
|
||||||
|
if (hgr.uuid == null) continue
|
||||||
|
habitGroups.add(hgr)
|
||||||
|
uuidToHabitGroup[hgr.uuid] = hgr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +342,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
init {
|
init {
|
||||||
habits = LinkedList()
|
habits = LinkedList()
|
||||||
|
habitGroups = LinkedList()
|
||||||
checkmarks = HashMap()
|
checkmarks = HashMap()
|
||||||
scores = HashMap()
|
scores = HashMap()
|
||||||
notes = HashMap()
|
notes = HashMap()
|
||||||
@@ -275,19 +351,19 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
private inner class RefreshTask : Task {
|
private inner class RefreshTask : Task {
|
||||||
private val newData: CacheData
|
private val newData: CacheData
|
||||||
private val targetId: Long?
|
private val targetUUID: String?
|
||||||
private var isCancelled = false
|
private var isCancelled = false
|
||||||
private var runner: TaskRunner? = null
|
private var runner: TaskRunner? = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
newData = CacheData()
|
newData = CacheData()
|
||||||
targetId = null
|
targetUUID = null
|
||||||
isCancelled = false
|
isCancelled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(targetId: Long) {
|
constructor(targetUUID: String) {
|
||||||
newData = CacheData()
|
newData = CacheData()
|
||||||
this.targetId = targetId
|
this.targetUUID = targetUUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -307,8 +383,8 @@ class HabitCardListCache @Inject constructor(
|
|||||||
for (position in newData.habits.indices) {
|
for (position in newData.habits.indices) {
|
||||||
if (isCancelled) return
|
if (isCancelled) return
|
||||||
val habit = newData.habits[position]
|
val habit = newData.habits[position]
|
||||||
if (targetId != null && targetId != habit.id) continue
|
if (targetUUID != null && targetUUID != habit.uuid) continue
|
||||||
newData.scores[habit.id] = habit.scores[today].value
|
newData.scores[habit.uuid] = habit.scores[today].value
|
||||||
val list: MutableList<Int> = ArrayList()
|
val list: MutableList<Int> = ArrayList()
|
||||||
val notes: MutableList<String> = ArrayList()
|
val notes: MutableList<String> = ArrayList()
|
||||||
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
||||||
@@ -316,10 +392,18 @@ class HabitCardListCache @Inject constructor(
|
|||||||
notes.add(note)
|
notes.add(note)
|
||||||
}
|
}
|
||||||
val entries = list.toTypedArray()
|
val entries = list.toTypedArray()
|
||||||
newData.checkmarks[habit.id] = ArrayUtils.toPrimitive(entries)
|
newData.checkmarks[habit.uuid] = ArrayUtils.toPrimitive(entries)
|
||||||
newData.notes[habit.id] = notes.toTypedArray()
|
newData.notes[habit.uuid] = notes.toTypedArray()
|
||||||
runner!!.publishProgress(this, position)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -340,15 +424,29 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun performInsert(habit: Habit, position: Int) {
|
private fun performInsert(habit: Habit, position: Int) {
|
||||||
val id = habit.id
|
val uuid = habit.uuid
|
||||||
data.habits.add(position, habit)
|
data.habits.add(position, habit)
|
||||||
data.idToHabit[id] = habit
|
data.uuidToHabit[uuid] = habit
|
||||||
data.scores[id] = newData.scores[id]!!
|
data.scores[uuid] = newData.scores[uuid]!!
|
||||||
data.checkmarks[id] = newData.checkmarks[id]!!
|
data.checkmarks[uuid] = newData.checkmarks[uuid]!!
|
||||||
data.notes[id] = newData.notes[id]!!
|
data.notes[uuid] = newData.notes[uuid]!!
|
||||||
listener.onItemInserted(position)
|
listener.onItemInserted(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun performInsert(habitGroup: HabitGroup, position: Int) {
|
||||||
|
val newPosition = if (position < data.habits.size) {
|
||||||
|
data.habits.size
|
||||||
|
} else {
|
||||||
|
position
|
||||||
|
}
|
||||||
|
val uuid = habitGroup.uuid
|
||||||
|
data.habitGroups.add(newPosition - data.habits.size, habitGroup)
|
||||||
|
data.uuidToHabitGroup[uuid] = habitGroup
|
||||||
|
data.scores[uuid] = newData.scores[uuid]!!
|
||||||
|
listener.onItemInserted(newPosition)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun performMove(
|
private fun performMove(
|
||||||
habit: Habit,
|
habit: Habit,
|
||||||
@@ -359,7 +457,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
|
|
||||||
// Workaround for https://github.com/iSoron/uhabits/issues/968
|
// Workaround for https://github.com/iSoron/uhabits/issues/968
|
||||||
val checkedToPosition = if (toPosition > data.habits.size) {
|
val checkedToPosition = if (toPosition > data.habits.size) {
|
||||||
logger.error("performMove: $toPosition is strictly higher than ${data.habits.size}")
|
logger.error("performMove: $toPosition for habit is strictly higher than ${data.habits.size}")
|
||||||
data.habits.size
|
data.habits.size
|
||||||
} else {
|
} else {
|
||||||
toPosition
|
toPosition
|
||||||
@@ -369,57 +467,114 @@ class HabitCardListCache @Inject constructor(
|
|||||||
listener.onItemMoved(fromPosition, checkedToPosition)
|
listener.onItemMoved(fromPosition, checkedToPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun performMove(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int
|
||||||
|
) {
|
||||||
|
data.habitGroups.removeAt(fromPosition)
|
||||||
|
|
||||||
|
// 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
|
||||||
private fun performUpdate(id: Long, position: Int) {
|
private fun performUpdate(uuid: String, position: Int) {
|
||||||
val oldScore = data.scores[id]!!
|
|
||||||
val oldCheckmarks = data.checkmarks[id]
|
|
||||||
val oldNoteIndicators = data.notes[id]
|
|
||||||
val newScore = newData.scores[id]!!
|
|
||||||
val newCheckmarks = newData.checkmarks[id]!!
|
|
||||||
val newNoteIndicators = newData.notes[id]!!
|
|
||||||
var unchanged = true
|
var unchanged = true
|
||||||
|
val oldScore = data.scores[uuid]!!
|
||||||
|
val newScore = newData.scores[uuid]!!
|
||||||
if (oldScore != newScore) unchanged = false
|
if (oldScore != newScore) unchanged = false
|
||||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
|
||||||
if (!Arrays.equals(oldNoteIndicators, newNoteIndicators)) unchanged = false
|
if (position < data.habits.size) {
|
||||||
|
val oldCheckmarks = data.checkmarks[uuid]
|
||||||
|
val newCheckmarks = newData.checkmarks[uuid]!!
|
||||||
|
val oldNoteIndicators = data.notes[uuid]
|
||||||
|
val newNoteIndicators = newData.notes[uuid]!!
|
||||||
|
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
||||||
|
if (!Arrays.equals(oldNoteIndicators, newNoteIndicators)) unchanged = false
|
||||||
|
if (unchanged) return
|
||||||
|
data.checkmarks[uuid] = newCheckmarks
|
||||||
|
data.notes[uuid] = newNoteIndicators
|
||||||
|
}
|
||||||
|
|
||||||
if (unchanged) return
|
if (unchanged) return
|
||||||
data.scores[id] = newScore
|
data.scores[uuid] = newScore
|
||||||
data.checkmarks[id] = newCheckmarks
|
|
||||||
data.notes[id] = newNoteIndicators
|
|
||||||
listener.onItemChanged(position)
|
listener.onItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun processPosition(currentPosition: Int) {
|
private fun processPosition(currentPosition: Int) {
|
||||||
val habit = newData.habits[currentPosition]
|
if (currentPosition < newData.habits.size) {
|
||||||
val id = habit.id
|
val habit = newData.habits[currentPosition]
|
||||||
val prevPosition = data.habits.indexOf(habit)
|
val uuid = habit.uuid
|
||||||
if (prevPosition < 0) {
|
val prevPosition = data.habits.indexOf(habit)
|
||||||
performInsert(habit, currentPosition)
|
if (prevPosition < 0) {
|
||||||
} else {
|
performInsert(habit, currentPosition)
|
||||||
if (prevPosition != currentPosition) {
|
} else {
|
||||||
performMove(
|
if (prevPosition != currentPosition) {
|
||||||
habit,
|
performMove(
|
||||||
prevPosition,
|
habit,
|
||||||
currentPosition
|
prevPosition,
|
||||||
)
|
currentPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (uuid == null) throw NullPointerException()
|
||||||
|
performUpdate(uuid, currentPosition)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val habitGroup = newData.habitGroups[currentPosition - data.habits.size]
|
||||||
|
val uuid = habitGroup.uuid
|
||||||
|
val prevPosition = data.habitGroups.indexOf(habitGroup) + data.habits.size
|
||||||
|
if (prevPosition < 0) {
|
||||||
|
performInsert(habitGroup, currentPosition)
|
||||||
|
} else {
|
||||||
|
if (prevPosition != currentPosition) {
|
||||||
|
performMove(
|
||||||
|
habitGroup,
|
||||||
|
prevPosition,
|
||||||
|
currentPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (uuid == null) throw NullPointerException()
|
||||||
|
performUpdate(uuid, currentPosition)
|
||||||
}
|
}
|
||||||
if (id == null) throw NullPointerException()
|
|
||||||
performUpdate(id, currentPosition)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun processRemovedHabits() {
|
private fun processRemovedHabits() {
|
||||||
val before: Set<Long?> = data.idToHabit.keys
|
val before: Set<String?> = data.uuidToHabit.keys
|
||||||
val after: Set<Long?> = newData.idToHabit.keys
|
val after: Set<String?> = newData.uuidToHabit.keys
|
||||||
val removed: MutableSet<Long?> = TreeSet(before)
|
val removed: MutableSet<String?> = TreeSet(before)
|
||||||
removed.removeAll(after)
|
removed.removeAll(after)
|
||||||
for (id in removed) remove(id!!)
|
for (uuid in removed) 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!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filteredHabits = allHabits
|
filteredHabits = allHabits
|
||||||
|
filteredHabitGroups = allHabitGroups
|
||||||
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.commands.CommandRunner
|
|||||||
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
|
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
|
||||||
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
|
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback
|
import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback
|
||||||
@@ -88,7 +89,9 @@ class ListHabitsSelectionMenuBehavior @Inject constructor(
|
|||||||
interface Adapter {
|
interface Adapter {
|
||||||
fun clearSelection()
|
fun clearSelection()
|
||||||
fun getSelected(): List<Habit>
|
fun getSelected(): List<Habit>
|
||||||
|
fun getSelectedHabitGroups(): List<HabitGroup>
|
||||||
fun performRemove(selected: List<Habit>)
|
fun performRemove(selected: List<Habit>)
|
||||||
|
fun performRemoveHabitGroup(selected: List<HabitGroup>)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Screen {
|
interface Screen {
|
||||||
|
|||||||
Reference in New Issue
Block a user