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)
|
||||
.map { it.value }.toIntArray()
|
||||
|
||||
view = component.getHabitCardViewFactory().create().apply {
|
||||
view = component.getHabitCardViewFactory().createHabitCard().apply {
|
||||
habit = habit1
|
||||
values = entries
|
||||
score = habit1.scores[today].value
|
||||
|
||||
@@ -148,7 +148,7 @@ class ListHabitsRootView @Inject constructor(
|
||||
|
||||
private fun updateEmptyView() {
|
||||
if (listAdapter.itemCount == 0) {
|
||||
if (listAdapter.hasNoHabit()) {
|
||||
if (listAdapter.hasNoHabit() && listAdapter.hasNoHabitGroup()) {
|
||||
llEmpty.showEmpty()
|
||||
} else {
|
||||
llEmpty.showDone()
|
||||
|
||||
@@ -82,7 +82,7 @@ class ListHabitsSelectionMenu @Inject constructor(
|
||||
itemArchive.isVisible = behavior.canArchive()
|
||||
itemUnarchive.isVisible = behavior.canUnarchive()
|
||||
itemNotify.isVisible = prefs.isDeveloper
|
||||
activeActionMode?.title = listAdapter.selected.size.toString()
|
||||
activeActionMode?.title = listAdapter.selectedHabits.size.toString()
|
||||
return true
|
||||
}
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
@@ -117,7 +117,7 @@ class ListHabitsSelectionMenu @Inject constructor(
|
||||
}
|
||||
|
||||
R.id.action_notify -> {
|
||||
for (h in listAdapter.selected)
|
||||
for (h in listAdapter.selectedHabits)
|
||||
notificationTray.show(h, DateUtils.getToday(), 0)
|
||||
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
|
||||
|
||||
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.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitGroup
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
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.inject.ActivityScope
|
||||
import java.util.LinkedList
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -46,14 +48,16 @@ class HabitCardListAdapter @Inject constructor(
|
||||
private val cache: HabitCardListCache,
|
||||
private val preferences: Preferences,
|
||||
private val midnightTimer: MidnightTimer
|
||||
) : RecyclerView.Adapter<HabitCardViewHolder?>(),
|
||||
) : Adapter<HabitCardViewHolder?>(),
|
||||
HabitCardListCache.Listener,
|
||||
MidnightTimer.MidnightListener,
|
||||
ListHabitsMenuBehavior.Adapter,
|
||||
ListHabitsSelectionMenuBehavior.Adapter {
|
||||
val observable: ModelObservable = ModelObservable()
|
||||
private var listView: HabitCardListView? = null
|
||||
val selected: LinkedList<Habit> = LinkedList()
|
||||
val selectedHabits: LinkedList<Habit> = LinkedList()
|
||||
val selectedHabitGroups: LinkedList<HabitGroup> = LinkedList()
|
||||
|
||||
override fun atMidnight() {
|
||||
cache.refreshAllHabits()
|
||||
}
|
||||
@@ -66,17 +70,25 @@ class HabitCardListAdapter @Inject constructor(
|
||||
return cache.hasNoHabit()
|
||||
}
|
||||
|
||||
fun hasNoHabitGroup(): Boolean {
|
||||
return cache.hasNoHabitGroup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all items as not selected.
|
||||
*/
|
||||
override fun clearSelection() {
|
||||
selected.clear()
|
||||
selectedHabits.clear()
|
||||
notifyDataSetChanged()
|
||||
observable.notifyListeners()
|
||||
}
|
||||
|
||||
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 {
|
||||
return cache.habitCount
|
||||
return cache.itemCount
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
val isSelectionEmpty: Boolean
|
||||
get() = selected.isEmpty()
|
||||
get() = selectedHabits.isEmpty() && selectedHabitGroups.isEmpty()
|
||||
val isSortable: Boolean
|
||||
get() = cache.primaryOrder == HabitList.Order.BY_POSITION
|
||||
|
||||
@@ -122,11 +161,18 @@ class HabitCardListAdapter @Inject constructor(
|
||||
) {
|
||||
if (listView == null) return
|
||||
val habit = cache.getHabitByPosition(position)
|
||||
val score = cache.getScore(habit!!.id!!)
|
||||
val checkmarks = cache.getCheckmarks(habit.id!!)
|
||||
val notes = cache.getNotes(habit.id!!)
|
||||
val selected = selected.contains(habit)
|
||||
listView!!.bindCardView(holder, habit, score, checkmarks, notes, selected)
|
||||
if (habit != null) {
|
||||
val score = cache.getScore(habit.uuid!!)
|
||||
val checkmarks = cache.getCheckmarks(habit.uuid!!)
|
||||
val notes = cache.getNotes(habit.uuid!!)
|
||||
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) {
|
||||
@@ -141,8 +187,22 @@ class HabitCardListAdapter @Inject constructor(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): HabitCardViewHolder {
|
||||
val view = listView!!.createHabitCardView()
|
||||
return HabitCardViewHolder(view)
|
||||
if (viewType == 0) {
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
fun toggleSelection(position: Int) {
|
||||
val h = getItem(position) ?: return
|
||||
val k = selected.indexOf(h)
|
||||
if (k < 0) selected.add(h) else selected.remove(h)
|
||||
notifyDataSetChanged()
|
||||
val h = cache.getHabitByPosition(position)
|
||||
val hgr = cache.getHabitGroupByPosition(position)
|
||||
if (h != null) {
|
||||
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 {
|
||||
|
||||
@@ -36,6 +36,7 @@ import dagger.Lazy
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.common.views.BundleSavedState
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitGroup
|
||||
import org.isoron.uhabits.inject.ActivityContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -79,7 +80,11 @@ class HabitCardListView(
|
||||
}
|
||||
|
||||
fun createHabitCardView(): HabitCardView {
|
||||
return cardViewFactory.create()
|
||||
return cardViewFactory.createHabitCard()
|
||||
}
|
||||
|
||||
fun createHabitGroupCardView(): HabitGroupCardView {
|
||||
return cardViewFactory.createHabitGroupCard()
|
||||
}
|
||||
|
||||
fun bindCardView(
|
||||
@@ -110,8 +115,28 @@ class HabitCardListView(
|
||||
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) {
|
||||
(holder.itemView as HabitCardView).dataOffset = dataOffset
|
||||
(holder.itemView as? HabitCardView)?.dataOffset = dataOffset
|
||||
attachedHolders.add(holder)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.isoron.platform.gui.toInt
|
||||
import org.isoron.uhabits.R
|
||||
import org.isoron.uhabits.activities.common.views.RingView
|
||||
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.Timestamp
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
|
||||
@@ -55,7 +56,8 @@ class HabitCardViewFactory
|
||||
private val numberPanelFactory: NumberPanelViewFactory,
|
||||
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(
|
||||
@@ -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) {
|
||||
val background = innerFrame.background
|
||||
background.setHotspot(x, y)
|
||||
|
||||
@@ -21,4 +21,4 @@ package org.isoron.uhabits.activities.habits.list.views
|
||||
|
||||
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.dp
|
||||
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(
|
||||
@ActivityContext context: Context,
|
||||
@@ -56,6 +47,7 @@ class HabitGroupCardView(
|
||||
scoreRing.setPrecision(1.0f / 16)
|
||||
}
|
||||
|
||||
var addButtonView: AddButtonView
|
||||
private var innerFrame: LinearLayout
|
||||
private var label: TextView
|
||||
private var scoreRing: RingView
|
||||
@@ -83,6 +75,8 @@ class HabitGroupCardView(
|
||||
}
|
||||
}
|
||||
|
||||
addButtonView = AddButtonView(context)
|
||||
|
||||
innerFrame = LinearLayout(context).apply {
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
@@ -91,6 +85,7 @@ class HabitGroupCardView(
|
||||
|
||||
addView(scoreRing)
|
||||
addView(label)
|
||||
addView(addButtonView)
|
||||
|
||||
setOnTouchListener { v, event ->
|
||||
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_check"></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_bell_o"></string>
|
||||
<string translatable="false" name="fa_calendar"></string>
|
||||
|
||||
Reference in New Issue
Block a user