Replace JVM stdlib imports with Kotlin stdlib for KMP migration
This commit is contained in:
parent
110f594d09
commit
7a48277478
2
build.sh
2
build.sh
@ -378,6 +378,8 @@ main() {
|
||||
build)
|
||||
shift; _parse_opts "$@"
|
||||
clean
|
||||
log_info "Formatting code..."
|
||||
gradle_run ktlintFormat || fail
|
||||
core_build
|
||||
android_build
|
||||
;;
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.io
|
||||
|
||||
expect fun format(format: String, arg: String): String
|
||||
expect fun format(format: String, arg: Int): String
|
||||
expect fun format(format: String, arg: Double): String
|
||||
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.io
|
||||
|
||||
actual fun format(format: String, arg: String): String =
|
||||
String.format(format, arg)
|
||||
|
||||
actual fun format(format: String, arg: Int): String =
|
||||
String.format(format, arg)
|
||||
|
||||
actual fun format(format: String, arg: Double): String =
|
||||
String.format(format, arg)
|
||||
@ -22,14 +22,13 @@ import me.tatarka.inject.annotations.Inject
|
||||
import org.isoron.uhabits.core.AppScope
|
||||
import org.isoron.uhabits.core.tasks.Task
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import java.util.LinkedList
|
||||
|
||||
@AppScope
|
||||
@Inject
|
||||
open class CommandRunner(
|
||||
private val taskRunner: TaskRunner
|
||||
) {
|
||||
private val listeners: LinkedList<Listener> = LinkedList()
|
||||
private val listeners: MutableList<Listener> = mutableListOf()
|
||||
|
||||
open fun run(command: Command) {
|
||||
taskRunner.execute(
|
||||
|
||||
@ -26,14 +26,10 @@ import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||
import java.util.ArrayList
|
||||
import java.util.Calendar
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ThreadSafe
|
||||
open class EntryList {
|
||||
|
||||
private val entriesByDate: HashMap<LocalDate, Entry> = HashMap()
|
||||
@ -304,7 +300,7 @@ private fun truncateDate(
|
||||
*/
|
||||
fun List<Entry>.groupedSum(
|
||||
truncateField: TruncateField,
|
||||
firstWeekday: Int = Calendar.SATURDAY,
|
||||
firstWeekday: Int = 7,
|
||||
isNumerical: Boolean
|
||||
): List<Entry> {
|
||||
val firstWeekdayEnum = DayOfWeek.values()[firstWeekday - 1]
|
||||
@ -332,7 +328,7 @@ fun List<Entry>.groupedSum(
|
||||
*/
|
||||
fun List<Entry>.countSkippedDays(
|
||||
truncateField: TruncateField,
|
||||
firstWeekday: Int = Calendar.SATURDAY
|
||||
firstWeekday: Int = 7
|
||||
): List<Entry> {
|
||||
val firstWeekdayEnum = DayOfWeek.values()[firstWeekday - 1]
|
||||
return this.map { (date, value) ->
|
||||
|
||||
@ -19,8 +19,10 @@
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import org.isoron.platform.time.getToday
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
data class Habit(
|
||||
var color: PaletteColor = PaletteColor(8),
|
||||
var description: String = "",
|
||||
@ -42,7 +44,7 @@ data class Habit(
|
||||
val streaks: StreakList
|
||||
) {
|
||||
init {
|
||||
if (uuid == null) this.uuid = UUID.randomUUID().toString().replace("-", "")
|
||||
if (uuid == null) this.uuid = Uuid.random().toHexString()
|
||||
}
|
||||
|
||||
var observable = ModelObservable()
|
||||
|
||||
@ -19,10 +19,9 @@
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import com.opencsv.CSVWriter
|
||||
import org.isoron.platform.io.format
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
import java.util.LinkedList
|
||||
import java.util.Locale
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
@ -125,8 +124,7 @@ abstract class HabitList : Iterable<Habit> {
|
||||
* Removes all the habits from the list.
|
||||
*/
|
||||
open fun removeAll() {
|
||||
val copy: MutableList<Habit> = LinkedList()
|
||||
for (h in this) copy.add(h)
|
||||
val copy = toList()
|
||||
for (h in copy) remove(h)
|
||||
observable.notifyListeners()
|
||||
}
|
||||
@ -199,7 +197,7 @@ abstract class HabitList : Iterable<Habit> {
|
||||
for (habit in this) {
|
||||
val (numerator, denominator) = habit.frequency
|
||||
val cols = arrayOf(
|
||||
String.format(Locale.US, "%03d", indexOf(habit) + 1),
|
||||
format("%03d", indexOf(habit) + 1),
|
||||
habit.name,
|
||||
habit.type.name,
|
||||
habit.question,
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
enum class HabitType(val value: Int) {
|
||||
YES_NO(0), NUMERICAL(1);
|
||||
|
||||
|
||||
@ -18,14 +18,10 @@
|
||||
*/
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import java.util.LinkedList
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
* A ModelObservable allows objects to subscribe themselves to it and receive
|
||||
* notifications whenever the model is changed.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class ModelObservable {
|
||||
private val listeners: MutableList<Listener>
|
||||
|
||||
@ -80,6 +76,6 @@ class ModelObservable {
|
||||
* Creates a new ModelObservable with no listeners.
|
||||
*/
|
||||
init {
|
||||
listeners = LinkedList()
|
||||
listeners = mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
enum class NumericalHabitType(val value: Int) {
|
||||
AT_LEAST(0), AT_MOST(1);
|
||||
|
||||
|
||||
@ -20,16 +20,12 @@ package org.isoron.uhabits.core.models
|
||||
|
||||
import org.isoron.platform.time.LocalDate
|
||||
import org.isoron.uhabits.core.models.Score.Companion.compute
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ThreadSafe
|
||||
class ScoreList {
|
||||
|
||||
private val map = HashMap<LocalDate, Score>()
|
||||
private val map = mutableMapOf<LocalDate, Score>()
|
||||
|
||||
/**
|
||||
* Returns the score for a given day. If the date given happens before the first
|
||||
@ -52,7 +48,7 @@ class ScoreList {
|
||||
from: LocalDate,
|
||||
to: LocalDate
|
||||
): List<Score> {
|
||||
val result: MutableList<Score> = ArrayList()
|
||||
val result: MutableList<Score> = mutableListOf()
|
||||
if (from.isNewerThan(to)) return result
|
||||
var current = to
|
||||
while (!current.isOlderThan(from)) {
|
||||
|
||||
@ -18,10 +18,6 @@
|
||||
*/
|
||||
package org.isoron.uhabits.core.models
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder
|
||||
import java.util.Arrays
|
||||
|
||||
class WeekdayList {
|
||||
private val weekdays: BooleanArray
|
||||
|
||||
@ -35,7 +31,7 @@ class WeekdayList {
|
||||
}
|
||||
|
||||
constructor(weekdays: BooleanArray?) {
|
||||
this.weekdays = Arrays.copyOf(weekdays, 7)
|
||||
this.weekdays = weekdays!!.copyOf(7)
|
||||
}
|
||||
|
||||
val isEmpty: Boolean
|
||||
@ -60,13 +56,12 @@ class WeekdayList {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
val that = other as WeekdayList
|
||||
return EqualsBuilder().append(weekdays, that.weekdays).isEquals
|
||||
if (other !is WeekdayList) return false
|
||||
return weekdays.contentEquals(other.weekdays)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return HashCodeBuilder(17, 37).append(weekdays).toHashCode()
|
||||
return weekdays.contentHashCode()
|
||||
}
|
||||
|
||||
override fun toString() = "{weekdays: [${weekdays.joinToString(separator = ",")}]}"
|
||||
|
||||
@ -21,16 +21,12 @@ package org.isoron.uhabits.core.models.memory
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
import java.util.ArrayList
|
||||
import java.util.Comparator
|
||||
import java.util.LinkedList
|
||||
import java.util.Objects
|
||||
|
||||
/**
|
||||
* In-memory implementation of [HabitList].
|
||||
*/
|
||||
class MemoryHabitList : HabitList {
|
||||
private val list = LinkedList<Habit>()
|
||||
private val list = mutableListOf<Habit>()
|
||||
|
||||
@get:Synchronized
|
||||
override var primaryOrder = Order.BY_POSITION
|
||||
@ -74,7 +70,7 @@ class MemoryHabitList : HabitList {
|
||||
val id = habit.id
|
||||
if (id != null && getById(id) != null) throw RuntimeException("duplicate id")
|
||||
if (id == null) habit.id = list.size.toLong()
|
||||
list.addLast(habit)
|
||||
list.add(habit)
|
||||
resort()
|
||||
}
|
||||
|
||||
@ -89,7 +85,7 @@ class MemoryHabitList : HabitList {
|
||||
|
||||
@Synchronized
|
||||
override fun getByUUID(uuid: String?): Habit? {
|
||||
for (h in list) if (Objects.requireNonNull(h.uuid) == uuid) return h
|
||||
for (h in list) if (h.uuid!! == uuid) return h
|
||||
return null
|
||||
}
|
||||
|
||||
@ -171,7 +167,7 @@ class MemoryHabitList : HabitList {
|
||||
|
||||
@Synchronized
|
||||
override fun iterator(): Iterator<Habit> {
|
||||
return ArrayList(list).iterator()
|
||||
return list.toMutableList().iterator()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
||||
@ -27,7 +27,6 @@ import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.models.Reminder
|
||||
import org.isoron.uhabits.core.models.WeekdayList
|
||||
import java.util.Objects.requireNonNull
|
||||
|
||||
/**
|
||||
* The SQLite database record corresponding to a [Habit].
|
||||
@ -110,7 +109,7 @@ class HabitRecord {
|
||||
reminderHour = null
|
||||
if (model.hasReminder()) {
|
||||
val reminder = model.reminder
|
||||
reminderHour = requireNonNull(reminder)!!.hour
|
||||
reminderHour = reminder!!.hour
|
||||
reminderMin = reminder!!.minute
|
||||
reminderDays = reminder.days.toInteger()
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ import org.isoron.platform.utils.StringUtils.Companion.joinLongs
|
||||
import org.isoron.platform.utils.StringUtils.Companion.splitLongs
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.ui.ThemeSwitcher
|
||||
import java.util.LinkedList
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -282,7 +281,7 @@ open class Preferences(private val storage: Storage) {
|
||||
}
|
||||
|
||||
init {
|
||||
listeners = LinkedList()
|
||||
listeners = mutableListOf()
|
||||
storage.onAttached(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,8 +29,6 @@ import org.isoron.uhabits.core.models.HabitList
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||
import org.isoron.uhabits.core.utils.DateUtils
|
||||
import java.util.Locale
|
||||
import java.util.Objects
|
||||
|
||||
@AppScope
|
||||
@Inject
|
||||
@ -57,19 +55,11 @@ class ReminderScheduler(
|
||||
sys.log("ReminderScheduler", "habit=" + habit.id + " has no reminder. Skipping.")
|
||||
return
|
||||
}
|
||||
var reminderTime = Objects.requireNonNull(habit.reminder)!!.timeInMillis
|
||||
var reminderTime = habit.reminder!!.timeInMillis
|
||||
val snoozeReminderTime = widgetPreferences.getSnoozeTime(habit.id!!)
|
||||
if (snoozeReminderTime != 0L) {
|
||||
val now = DateUtils.applyTimezone(DateUtils.getLocalTime())
|
||||
sys.log(
|
||||
"ReminderScheduler",
|
||||
String.format(
|
||||
Locale.US,
|
||||
"Habit %d has been snoozed until %d",
|
||||
habit.id,
|
||||
snoozeReminderTime
|
||||
)
|
||||
)
|
||||
sys.log("ReminderScheduler", "Habit ${habit.id} has been snoozed until $snoozeReminderTime")
|
||||
if (snoozeReminderTime > now) {
|
||||
sys.log("ReminderScheduler", "Snooze time is in the future. Accepting.")
|
||||
reminderTime = snoozeReminderTime
|
||||
@ -93,16 +83,7 @@ class ReminderScheduler(
|
||||
return
|
||||
}
|
||||
val timestamp = DateUtils.getStartOfDayWithOffset(DateUtils.removeTimezone(reminderTime), 0, 0)
|
||||
sys.log(
|
||||
"ReminderScheduler",
|
||||
String.format(
|
||||
Locale.US,
|
||||
"reminderTime=%d removeTimezone=%d timestamp=%d",
|
||||
reminderTime,
|
||||
DateUtils.removeTimezone(reminderTime),
|
||||
timestamp
|
||||
)
|
||||
)
|
||||
sys.log("ReminderScheduler", "reminderTime=$reminderTime removeTimezone=${DateUtils.removeTimezone(reminderTime)} timestamp=$timestamp")
|
||||
sys.scheduleShowReminder(reminderTime, habit, timestamp)
|
||||
}
|
||||
|
||||
|
||||
@ -18,13 +18,11 @@
|
||||
*/
|
||||
package org.isoron.uhabits.core.tasks
|
||||
|
||||
import java.util.LinkedList
|
||||
|
||||
class SingleThreadTaskRunner : TaskRunner {
|
||||
override val activeTaskCount: Int
|
||||
get() = 0
|
||||
|
||||
private val listeners: MutableList<TaskRunner.Listener> = LinkedList()
|
||||
private val listeners: MutableList<TaskRunner.Listener> = mutableListOf()
|
||||
override fun addListener(listener: TaskRunner.Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
@ -30,9 +30,6 @@ import org.isoron.uhabits.core.models.NumericalHabitType
|
||||
import org.isoron.uhabits.core.preferences.Preferences
|
||||
import org.isoron.uhabits.core.tasks.Task
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import java.util.HashMap
|
||||
import java.util.Locale
|
||||
import java.util.Objects
|
||||
|
||||
@AppScope
|
||||
@Inject
|
||||
@ -42,7 +39,7 @@ class NotificationTray(
|
||||
private val preferences: Preferences,
|
||||
private val systemTray: SystemTray
|
||||
) : CommandRunner.Listener, Preferences.Listener {
|
||||
private val active: HashMap<Habit, NotificationData> = HashMap()
|
||||
private val active: MutableMap<Habit, NotificationData> = mutableMapOf()
|
||||
fun cancel(habit: Habit) {
|
||||
val notificationId = getNotificationId(habit)
|
||||
systemTray.removeNotification(notificationId)
|
||||
@ -123,43 +120,19 @@ class NotificationTray(
|
||||
override fun onPostExecute() {
|
||||
systemTray.log("Showing notification for habit=" + habit.id)
|
||||
if (isCompleted && habit.targetType != NumericalHabitType.AT_MOST) {
|
||||
systemTray.log(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"Habit %d already checked. Skipping.",
|
||||
habit.id
|
||||
)
|
||||
)
|
||||
systemTray.log("Habit ${habit.id} already checked. Skipping.")
|
||||
return
|
||||
}
|
||||
if (!habit.hasReminder()) {
|
||||
systemTray.log(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"Habit %d does not have a reminder. Skipping.",
|
||||
habit.id
|
||||
)
|
||||
)
|
||||
systemTray.log("Habit ${habit.id} does not have a reminder. Skipping.")
|
||||
return
|
||||
}
|
||||
if (habit.isArchived) {
|
||||
systemTray.log(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"Habit %d is archived. Skipping.",
|
||||
habit.id
|
||||
)
|
||||
)
|
||||
systemTray.log("Habit ${habit.id} is archived. Skipping.")
|
||||
return
|
||||
}
|
||||
if (!shouldShowReminderToday()) {
|
||||
systemTray.log(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"Habit %d not supposed to run today. Skipping.",
|
||||
habit.id
|
||||
)
|
||||
)
|
||||
systemTray.log("Habit ${habit.id} not supposed to run today. Skipping.")
|
||||
return
|
||||
}
|
||||
systemTray.showNotification(
|
||||
@ -173,7 +146,7 @@ class NotificationTray(
|
||||
private fun shouldShowReminderToday(): Boolean {
|
||||
if (!habit.hasReminder()) return false
|
||||
val reminder = habit.reminder
|
||||
val reminderDays = Objects.requireNonNull(reminder)!!.days.toArray()
|
||||
val reminderDays = reminder!!.days.toArray()
|
||||
val weekday = (date.dayOfWeek.daysSinceSunday + 1) % 7
|
||||
return reminderDays[weekday]
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
package org.isoron.uhabits.core.ui.screens.habits.list
|
||||
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.isoron.platform.time.getToday
|
||||
import org.isoron.uhabits.core.AppScope
|
||||
import org.isoron.uhabits.core.commands.Command
|
||||
@ -32,11 +31,6 @@ import org.isoron.uhabits.core.models.HabitList.Order
|
||||
import org.isoron.uhabits.core.models.HabitMatcher
|
||||
import org.isoron.uhabits.core.tasks.Task
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
import java.util.HashMap
|
||||
import java.util.LinkedList
|
||||
import java.util.TreeSet
|
||||
|
||||
/**
|
||||
* A HabitCardListCache fetches and keeps a cache of all the data necessary to
|
||||
@ -210,11 +204,11 @@ class HabitCardListCache(
|
||||
}
|
||||
|
||||
private inner class CacheData {
|
||||
val idToHabit: HashMap<Long?, Habit> = HashMap()
|
||||
val idToHabit: MutableMap<Long?, Habit> = mutableMapOf()
|
||||
val habits: MutableList<Habit>
|
||||
val checkmarks: HashMap<Long?, IntArray>
|
||||
val scores: HashMap<Long?, Double>
|
||||
val notes: HashMap<Long?, Array<String>>
|
||||
val checkmarks: MutableMap<Long?, IntArray>
|
||||
val scores: MutableMap<Long?, Double>
|
||||
val notes: MutableMap<Long?, Array<String>>
|
||||
|
||||
@Synchronized
|
||||
fun copyCheckmarksFrom(oldData: CacheData) {
|
||||
@ -267,10 +261,10 @@ class HabitCardListCache(
|
||||
* Creates a new CacheData without any content.
|
||||
*/
|
||||
init {
|
||||
habits = LinkedList()
|
||||
checkmarks = HashMap()
|
||||
scores = HashMap()
|
||||
notes = HashMap()
|
||||
habits = mutableListOf()
|
||||
checkmarks = mutableMapOf()
|
||||
scores = mutableMapOf()
|
||||
notes = mutableMapOf()
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,15 +304,14 @@ class HabitCardListCache(
|
||||
val habit = newData.habits[position]
|
||||
if (targetId != null && targetId != habit.id) continue
|
||||
newData.scores[habit.id] = habit.scores[today].value
|
||||
val list: MutableList<Int> = ArrayList()
|
||||
val notes: MutableList<String> = ArrayList()
|
||||
val checkmarkList = mutableListOf<Int>()
|
||||
val noteList = mutableListOf<String>()
|
||||
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
||||
list.add(value)
|
||||
notes.add(note)
|
||||
checkmarkList.add(value)
|
||||
noteList.add(note)
|
||||
}
|
||||
val entries = list.toTypedArray()
|
||||
newData.checkmarks[habit.id] = ArrayUtils.toPrimitive(entries)
|
||||
newData.notes[habit.id] = notes.toTypedArray()
|
||||
newData.checkmarks[habit.id] = checkmarkList.toIntArray()
|
||||
newData.notes[habit.id] = noteList.toTypedArray()
|
||||
runner!!.publishProgress(this, position)
|
||||
}
|
||||
}
|
||||
@ -380,8 +373,8 @@ class HabitCardListCache(
|
||||
val newNoteIndicators = newData.notes[id]!!
|
||||
var unchanged = true
|
||||
if (oldScore != newScore) unchanged = false
|
||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
||||
if (!Arrays.equals(oldNoteIndicators, newNoteIndicators)) unchanged = false
|
||||
if (!oldCheckmarks.contentEquals(newCheckmarks)) unchanged = false
|
||||
if (!oldNoteIndicators.contentEquals(newNoteIndicators)) unchanged = false
|
||||
if (unchanged) return
|
||||
data.scores[id] = newScore
|
||||
data.checkmarks[id] = newCheckmarks
|
||||
@ -413,8 +406,7 @@ class HabitCardListCache(
|
||||
private fun processRemovedHabits() {
|
||||
val before: Set<Long?> = data.idToHabit.keys
|
||||
val after: Set<Long?> = newData.idToHabit.keys
|
||||
val removed: MutableSet<Long?> = TreeSet(before)
|
||||
removed.removeAll(after)
|
||||
val removed = before - after
|
||||
for (id in removed) remove(id!!)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,9 +30,12 @@ import org.isoron.uhabits.core.tasks.ExportCSVTask
|
||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
||||
import java.io.File
|
||||
import java.util.Random
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.ln
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class ShowHabitMenuPresenter(
|
||||
private val commandRunner: CommandRunner,
|
||||
@ -86,16 +89,15 @@ class ShowHabitMenuPresenter(
|
||||
}
|
||||
|
||||
fun onRandomize() {
|
||||
val random = Random()
|
||||
habit.originalEntries.clear()
|
||||
var strength = 50.0
|
||||
for (i in 0 until 365 * 5) {
|
||||
if (i % 7 == 0) strength = max(0.0, min(100.0, strength + 10 * random.nextGaussian()))
|
||||
if (random.nextInt(100) > strength) continue
|
||||
if (i % 7 == 0) strength = max(0.0, min(100.0, strength + 10 * nextGaussian()))
|
||||
if (kotlin.random.Random.nextInt(100) > strength) continue
|
||||
var value = Entry.YES_MANUAL
|
||||
if (habit.isNumerical) {
|
||||
value =
|
||||
(1000 + 250 * random.nextGaussian() * strength / 100).toInt() * 1000
|
||||
(1000 + 250 * nextGaussian() * strength / 100).toInt() * 1000
|
||||
}
|
||||
habit.originalEntries.add(Entry(getToday().minus(i), value))
|
||||
}
|
||||
@ -103,6 +105,12 @@ class ShowHabitMenuPresenter(
|
||||
screen.refresh()
|
||||
}
|
||||
|
||||
private fun nextGaussian(): Double {
|
||||
val u1 = kotlin.random.Random.nextDouble()
|
||||
val u2 = kotlin.random.Random.nextDouble()
|
||||
return sqrt(-2.0 * ln(u1)) * cos(2.0 * PI * u2)
|
||||
}
|
||||
|
||||
enum class Message {
|
||||
COULD_NOT_EXPORT,
|
||||
HABIT_ARCHIVED,
|
||||
|
||||
@ -24,8 +24,6 @@ import org.isoron.platform.time.LocalDate
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.ui.views.Theme
|
||||
import java.util.HashMap
|
||||
|
||||
data class FrequencyCardState(
|
||||
val color: PaletteColor,
|
||||
val firstWeekday: DayOfWeek,
|
||||
|
||||
@ -26,7 +26,6 @@ import org.isoron.uhabits.core.models.PaletteColor
|
||||
import org.isoron.uhabits.core.models.countSkippedDays
|
||||
import org.isoron.uhabits.core.models.groupedSum
|
||||
import org.isoron.uhabits.core.ui.views.Theme
|
||||
import java.util.ArrayList
|
||||
import kotlin.math.max
|
||||
|
||||
data class TargetCardState(
|
||||
@ -135,21 +134,21 @@ class TargetCardPresenter {
|
||||
targetThisQuarter = max(0.0, targetThisQuarter - dailyTarget * skippedDaysThisQuarter)
|
||||
targetThisYear = max(0.0, targetThisYear - dailyTarget * skippedDaysThisYear)
|
||||
|
||||
val values = ArrayList<Double>()
|
||||
val values = mutableListOf<Double>()
|
||||
if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3)
|
||||
if (habit.frequency.denominator <= 7) values.add(valueThisWeek / 1e3)
|
||||
values.add(valueThisMonth / 1e3)
|
||||
values.add(valueThisQuarter / 1e3)
|
||||
values.add(valueThisYear / 1e3)
|
||||
|
||||
val targets = ArrayList<Double>()
|
||||
val targets = mutableListOf<Double>()
|
||||
if (habit.frequency.denominator <= 1) targets.add(targetToday)
|
||||
if (habit.frequency.denominator <= 7) targets.add(targetThisWeek)
|
||||
targets.add(targetThisMonth)
|
||||
targets.add(targetThisQuarter)
|
||||
targets.add(targetThisYear)
|
||||
|
||||
val intervals = ArrayList<Int>()
|
||||
val intervals = mutableListOf<Int>()
|
||||
if (habit.frequency.denominator <= 1) intervals.add(1)
|
||||
if (habit.frequency.denominator <= 7) intervals.add(7)
|
||||
intervals.add(30)
|
||||
|
||||
@ -23,26 +23,25 @@ import org.isoron.platform.gui.Canvas
|
||||
import org.isoron.platform.gui.Color
|
||||
import org.isoron.platform.gui.Font
|
||||
import org.isoron.platform.gui.View
|
||||
import java.lang.String.format
|
||||
import kotlin.math.round
|
||||
|
||||
fun Double.toShortString(): String = when {
|
||||
this >= 1e9 -> format("%.1fG", this / 1e9)
|
||||
this >= 1e8 -> format("%.0fM", this / 1e6)
|
||||
this >= 1e7 -> format("%.1fM", this / 1e6)
|
||||
this >= 1e6 -> format("%.1fM", this / 1e6)
|
||||
this >= 1e5 -> format("%.0fk", this / 1e3)
|
||||
this >= 1e4 -> format("%.1fk", this / 1e3)
|
||||
this >= 1e3 -> format("%.1fk", this / 1e3)
|
||||
this >= 1e2 -> format("%.0f", this)
|
||||
this >= 1e9 -> "%.1fG".format(this / 1e9)
|
||||
this >= 1e8 -> "%.0fM".format(this / 1e6)
|
||||
this >= 1e7 -> "%.1fM".format(this / 1e6)
|
||||
this >= 1e6 -> "%.1fM".format(this / 1e6)
|
||||
this >= 1e5 -> "%.0fk".format(this / 1e3)
|
||||
this >= 1e4 -> "%.1fk".format(this / 1e3)
|
||||
this >= 1e3 -> "%.1fk".format(this / 1e3)
|
||||
this >= 1e2 -> "%.0f".format(this)
|
||||
this >= 1e1 -> when {
|
||||
round(this) == this -> format("%.0f", this)
|
||||
else -> format("%.1f", this)
|
||||
round(this) == this -> "%.0f".format(this)
|
||||
else -> "%.1f".format(this)
|
||||
}
|
||||
else -> when {
|
||||
round(this) == this -> format("%.0f", this)
|
||||
round(this * 10) == this * 10 -> format("%.1f", this)
|
||||
else -> format("%.2f", this)
|
||||
round(this) == this -> "%.0f".format(this)
|
||||
round(this * 10) == this * 10 -> "%.1f".format(this)
|
||||
else -> "%.2f".format(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,6 @@ package org.isoron.uhabits.core.ui.views
|
||||
import org.isoron.platform.gui.Canvas
|
||||
import org.isoron.platform.gui.Color
|
||||
import org.isoron.platform.gui.View
|
||||
import java.lang.String.format
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -52,7 +51,7 @@ class Ring(
|
||||
if (label) {
|
||||
canvas.setColor(color)
|
||||
canvas.setFontSize(radius * 0.4)
|
||||
canvas.drawText(format("%.0f%%", percentage * 100), width / 2, height / 2)
|
||||
canvas.drawText("%.0f%%".format(percentage * 100), width / 2, height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.platform.io
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class StringsTest {
|
||||
@Test
|
||||
fun testFormat() {
|
||||
assertEquals("hello world!", format("hello %s!", "world"))
|
||||
assertEquals(" 5", format("%3d", 5))
|
||||
assertEquals("005", format("%03d", 5))
|
||||
assertEquals(" 45", format("%3d", 45))
|
||||
assertEquals("145", format("%3d", 145))
|
||||
assertEquals(" 13.42", format("%8.2f", 13.419187263))
|
||||
assertEquals("00013.42", format("%08.2f", 13.419187263))
|
||||
assertEquals("13.42 ", format("%-8.2f", 13.419187263))
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||
import org.junit.Test
|
||||
import java.util.Random
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class EntryListTest {
|
||||
|
||||
@ -27,7 +27,6 @@ import org.isoron.uhabits.core.BaseUnitTest
|
||||
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.ArrayList
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
open class BaseScoreListTest : BaseUnitTest() {
|
||||
@ -151,7 +150,7 @@ class YesNoScoreListTest : BaseScoreListTest() {
|
||||
// If the habit should be performed 3 times per week and the user misses 1 repetition
|
||||
// each week, score should converge to 66%.
|
||||
habit.frequency = Frequency(3, 7)
|
||||
val values = ArrayList<Int>()
|
||||
val values = mutableListOf<Int>()
|
||||
for (k in 0..99) {
|
||||
values.add(Entry.YES_MANUAL)
|
||||
values.add(Entry.YES_MANUAL)
|
||||
@ -175,7 +174,7 @@ class YesNoScoreListTest : BaseScoreListTest() {
|
||||
// If the user performs habit perfectly each week, but on different weekdays,
|
||||
// score should still converge to 100%
|
||||
habit.frequency = Frequency(1, 7)
|
||||
val values = ArrayList<Int>()
|
||||
val values = mutableListOf<Int>()
|
||||
for (k in 0..99) {
|
||||
// Week 0
|
||||
values.add(Entry.YES_MANUAL)
|
||||
@ -256,7 +255,7 @@ class YesNoScoreListTest : BaseScoreListTest() {
|
||||
habit.recompute()
|
||||
}
|
||||
|
||||
private fun check(values: ArrayList<Int>) {
|
||||
private fun check(values: MutableList<Int>) {
|
||||
val entries = habit.originalEntries
|
||||
for (i in values.indices) if (values[i] == Entry.YES_MANUAL) {
|
||||
entries.add(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user