Can create habit groups now

pull/2020/head
Dharanish 1 year ago
parent 08113a57ac
commit af3283e52f

@ -42,6 +42,14 @@
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.habits.edit.EditHabitGroupActivity"
android:exported="true">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />

@ -38,7 +38,7 @@ import javax.inject.Inject
* Provides data that backs a [HabitCardListView].
*
*
* The data if fetched and cached by a [HabitCardListCache]. This adapter
* The data is fetched and cached by a [HabitCardListCache]. This adapter
* also holds a list of items that have been selected.
*/
@ActivityScope

@ -0,0 +1,160 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.Context
import android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
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.HabitGroup
import org.isoron.uhabits.core.models.ModelObservable
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
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,
private val behavior: ListHabitsBehavior
) : FrameLayout(context),
ModelObservable.Listener {
var habitGroup: HabitGroup? = null
set(newHabitGroup) {
if (isAttachedToWindow) {
field?.observable?.removeListener(this)
newHabitGroup?.observable?.addListener(this)
}
field = newHabitGroup
if (newHabitGroup != null) copyAttributesFrom(newHabitGroup)
}
var score
get() = scoreRing.getPercentage().toDouble()
set(value) {
scoreRing.setPercentage(value.toFloat())
scoreRing.setPrecision(1.0f / 16)
}
private var innerFrame: LinearLayout
private var label: TextView
private var scoreRing: RingView
private var currentToggleTaskId = 0
init {
scoreRing = RingView(context).apply {
val thickness = dp(3f)
val margin = dp(8f).toInt()
val ringSize = dp(15f).toInt()
layoutParams = LinearLayout.LayoutParams(ringSize, ringSize).apply {
setMargins(margin, 0, margin, 0)
gravity = Gravity.CENTER
}
setThickness(thickness)
}
label = TextView(context).apply {
maxLines = 2
ellipsize = TextUtils.TruncateAt.END
layoutParams = LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f)
if (SDK_INT >= Build.VERSION_CODES.Q) {
breakStrategy = BREAK_STRATEGY_BALANCED
}
}
innerFrame = LinearLayout(context).apply {
gravity = Gravity.CENTER_VERTICAL
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
elevation = dp(1f)
addView(scoreRing)
addView(label)
setOnTouchListener { v, event ->
v.background.setHotspot(event.x, event.y)
false
}
}
clipToPadding = false
layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
val margin = dp(3f).toInt()
setPadding(margin, 0, margin, margin)
addView(innerFrame)
}
override fun onModelChange() {
Handler(Looper.getMainLooper()).post {
habitGroup?.let { copyAttributesFrom(it) }
}
}
override fun setSelected(isSelected: Boolean) {
super.setSelected(isSelected)
updateBackground(isSelected)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
habitGroup?.observable?.addListener(this)
}
override fun onDetachedFromWindow() {
habitGroup?.observable?.removeListener(this)
super.onDetachedFromWindow()
}
private fun copyAttributesFrom(hgr: HabitGroup) {
fun getActiveColor(hgr: HabitGroup): Int {
return when (hgr.isArchived) {
true -> sres.getColor(R.attr.contrast60)
false -> currentTheme().color(hgr.color).toInt()
}
}
val c = getActiveColor(hgr)
label.apply {
text = hgr.name
setTextColor(c)
}
scoreRing.apply {
setColor(c)
}
}
private fun updateBackground(isSelected: Boolean) {
val background = when (isSelected) {
true -> R.drawable.selected_box
false -> R.drawable.ripple
}
innerFrame.setBackgroundResource(background)
}
companion object {
fun (() -> Unit).delay(delayInMillis: Long) {
Handler(Looper.getMainLooper()).postDelayed(this, delayInMillis)
}
}
}

@ -0,0 +1,5 @@
package org.isoron.uhabits.activities.habits.list.views
import androidx.recyclerview.widget.RecyclerView
class HabitGroupCardViewHolder(itemView: HabitGroupCardView) : RecyclerView.ViewHolder(itemView)

@ -0,0 +1,19 @@
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
data class ArchiveHabitGroupsCommand(
val habitGroupList: HabitGroupList,
val selected: List<HabitGroup>
) : Command {
override fun run() {
for (hgr in selected) {
hgr.isArchived = true
for (h in hgr.habitList) {
h.isArchived = true
}
}
habitGroupList.update(selected)
}
}

@ -0,0 +1,16 @@
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
import org.isoron.uhabits.core.models.PaletteColor
data class ChangeHabitGroupColorCommand(
val habitGroupList: HabitGroupList,
val selected: List<HabitGroup>,
val newColor: PaletteColor
) : Command {
override fun run() {
for (hgr in selected) hgr.color = newColor
habitGroupList.update(selected)
}
}

@ -0,0 +1,13 @@
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
data class DeleteHabitGroupsCommand(
val habitGroupList: HabitGroupList,
val selected: List<HabitGroup>
) : Command {
override fun run() {
for (hgr in selected) habitGroupList.remove(hgr)
}
}

@ -0,0 +1,19 @@
package org.isoron.uhabits.core.commands
import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
data class UnarchiveHabitGroupsCommand(
val habitGroupList: HabitGroupList,
val selected: List<HabitGroup>
) : Command {
override fun run() {
for (hgr in selected) {
hgr.isArchived = false
for (h in hgr.habitList) {
h.isArchived = false
}
}
habitGroupList.update(selected)
}
}

@ -111,12 +111,8 @@ data class Habit(
return computedEntries.getKnown().lastOrNull()?.timestamp ?: DateUtils.getTodayWithOffset()
}
fun hierarchyLevel(): Int {
return if (parentID == null) {
0
} else {
1 + parent!!.hierarchyLevel()
}
fun isInGroup(): Boolean {
return (parentID != null)
}
fun copyFrom(other: Habit) {

@ -15,15 +15,12 @@ data class HabitGroup(
var uuid: String? = null,
var habitList: HabitList,
val scores: ScoreList,
val streaks: StreakList,
var parentID: Long? = null,
var parentUUID: String? = null
val streaks: StreakList
) {
init {
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
}
var parent: HabitGroup? = null
var observable = ModelObservable()
val uriString: String
@ -80,8 +77,6 @@ data class HabitGroup(
this.question = other.question
this.reminder = other.reminder
this.uuid = other.uuid
this.parentID = other.parentID
this.parentUUID = other.parentUUID
}
override fun equals(other: Any?): Boolean {
@ -97,8 +92,6 @@ data class HabitGroup(
if (question != other.question) return false
if (reminder != other.reminder) return false
if (uuid != other.uuid) return false
if (parentID != other.parentID) return false
if (parentUUID != other.parentUUID) return false
return true
}
@ -113,19 +106,9 @@ data class HabitGroup(
result = 31 * result + question.hashCode()
result = 31 * result + (reminder?.hashCode() ?: 0)
result = 31 * result + (uuid?.hashCode() ?: 0)
result = 31 * result + (parentID?.hashCode() ?: 0)
result = 31 * result + (parentUUID?.hashCode() ?: 0)
return result
}
fun hierarchyLevel(): Int {
return if (parentID == null) {
0
} else {
1 + parent!!.hierarchyLevel()
}
}
fun getHabitByUUIDDeep(uuid: String?): Habit? =
fun getHabitByUUID(uuid: String?): Habit? =
habitList.getByUUID(uuid)
}

@ -18,10 +18,10 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
protected val filter: HabitMatcher
/**
* Creates a new HabitList.
* Creates a new HabitGroupList.
*
* Depending on the implementation, this list can either be empty or be
* populated by some pre-existing habits, for example, from a certain
* populated by some pre-existing habitgroups, for example, from a certain
* database.
*/
constructor() {
@ -35,12 +35,12 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
}
/**
* Inserts a new habit in the list.
* Inserts a new habit group in the list.
*
* If the id of the habit is null, the list will assign it a new id, which
* If the id of the habit group 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.
* another habit group with same id, otherwise a RuntimeException will be thrown.
*
* @param habitGroup the habit to be inserted
* @throws IllegalArgumentException if the habit is already on the list.
@ -49,28 +49,28 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
abstract fun add(habitGroup: HabitGroup)
/**
* Returns the habit with specified id.
* Returns the habit group with specified id.
*
* @param id the id of the habit
* @return the habit, or null if none exist
* @param id the id of the habit group
* @return the habit group, or null if none exist
*/
abstract fun getById(id: Long): HabitGroup?
/**
* Returns the habit with specified UUID.
* Returns the habit group with specified UUID.
*
* @param uuid the UUID of the habit
* @return the habit, or null if none exist
* @param uuid the UUID of the habit group
* @return the habit group, or null if none exist
*/
abstract fun getByUUID(uuid: String?): HabitGroup?
/**
* Returns the habit with the specified UUID which is
* present at any hierarchy within this list.
* present in any of the habit groups within this habit group list.
*/
fun getHabitByUUIDDeep(uuid: String?): Habit? {
fun getHabitByUUID(uuid: String?): Habit? {
for (hgr in this) {
val habit = hgr.getHabitByUUIDDeep(uuid)
val habit = hgr.getHabitByUUID(uuid)
if (habit != null) {
return habit
}
@ -79,46 +79,46 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
}
/**
* Returns the habit that occupies a certain position.
* Returns the habit group that occupies a certain position.
*
* @param position the position of the desired habit
* @return the habit at that position
* @param position the position of the desired habit group
* @return the habit group at that position
* @throws IndexOutOfBoundsException when the position is invalid
*/
abstract fun getByPosition(position: Int): HabitGroup
/**
* Returns the list of habits that match a given condition.
* Returns the list of habit groups that match a given condition.
*
* @param matcher the matcher that checks the condition
* @return the list of matching habits
* @return the list of matching habit groups
*/
abstract fun getFiltered(matcher: HabitMatcher?): HabitGroupList
abstract var primaryOrder: Order
abstract var secondaryOrder: Order
/**
* Returns the index of the given habit in the list, or -1 if the list does
* not contain the habit.
* Returns the index of the given habit group in the list, or -1 if the list does
* not contain the habit group.
*
* @param h the habit
* @return the index of the habit, or -1 if not in the list
* @param h the habit group
* @return the index of the habit group, or -1 if not in the list
*/
abstract fun indexOf(h: HabitGroup): Int
val isEmpty: Boolean
get() = size() == 0
/**
* Removes the given habit from the list.
* Removes the given habit group from the list.
*
* If the given habit is not in the list, does nothing.
* If the given habit group is not in the list, does nothing.
*
* @param h the habit to be removed.
* @param h the habit group to be removed.
*/
abstract fun remove(h: HabitGroup)
/**
* Removes all the habits from the list.
* Removes all the habit groups from the list.
*/
open fun removeAll() {
val copy: MutableList<HabitGroup> = LinkedList()
@ -128,43 +128,49 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
}
/**
* Changes the position of a habit in the list.
* Changes the position of a habit group in the list.
*
* @param from the habit that should be moved
* @param to the habit that currently occupies the desired position
* @param from the habit group that should be moved
* @param to the habit group that currently occupies the desired position
*/
abstract fun reorder(from: HabitGroup, to: HabitGroup)
open fun repair() {}
/**
* Returns the number of habits in this list.
* Returns the number of habit groups in this list.
*
* @return number of habits
* @return number of habit groups
*/
abstract fun size(): Int
/**
* Notifies the list that a certain list of habits has been modified.
* Notifies the list that a certain list of habit groups has been modified.
*
* Depending on the implementation, this operation might trigger a write to
* disk, or do nothing at all. To make sure that the habits get persisted,
* disk, or do nothing at all. To make sure that the habit groups get persisted,
* this operation must be called.
*
* @param habitGroups the list of habits that have been modified.
* @param habitGroups the list of habit groups that have been modified.
*/
abstract fun update(habitGroups: List<HabitGroup>)
/**
* Notifies the list that a certain habit has been modified.
* Notifies the list that a certain habit group has been modified.
*
* See [.update] for more details.
*
* @param habitGroup the habit that has been modified.
* @param habitGroup the habit groups that has been modified.
*/
fun update(habitGroup: HabitGroup) {
update(listOf(habitGroup))
}
/**
* For an empty Habit group list, and a given list of habits,
* populate the habit groups with their appropriate habits
*
* @param habitList list of habits to add to the groups
* */
fun populateGroupsWith(habitList: HabitList) {
val toRemove = mutableListOf<String?>()
for (habit in habitList) {
@ -187,10 +193,9 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
}
/**
* Writes the list of habits to the given writer, in CSV format. There is
* one line for each habit, containing the fields name, description,
* frequency numerator, frequency denominator and color. The color is
* written in HTML format (#000000).
* Writes the list of habit groups to the given writer, in CSV format. There is
* one line for each habit group, containing the fields name, description,
* , and color. The color is written in HTML format (#000000).
*
* @param out the writer that will receive the result
* @throws IOException if write operations fail
@ -208,13 +213,13 @@ abstract class HabitGroupList : Iterable<HabitGroup> {
)
val csv = CSVWriter(out)
csv.writeNext(header, false)
for (habit in this) {
for (hgr in this) {
val cols = arrayOf(
String.format("%03d", indexOf(habit) + 1),
habit.name,
habit.question,
habit.description,
habit.color.toCsvColor()
String.format("%03d", indexOf(hgr) + 1),
hgr.name,
hgr.question,
hgr.description,
hgr.color.toCsvColor()
)
csv.writeNext(cols, false)
}

Loading…
Cancel
Save