From 7a48277478b3db73824740f8ab8f4563fd746153 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sun, 5 Apr 2026 16:41:55 -0500 Subject: [PATCH] Replace JVM stdlib imports with Kotlin stdlib for KMP migration --- build.sh | 2 + .../kotlin/org/isoron/platform/io/Strings.kt | 24 +++++++++++ .../org/isoron/platform/io/JavaStrings.kt | 29 +++++++++++++ .../uhabits/core/commands/CommandRunner.kt | 3 +- .../isoron/uhabits/core/models/EntryList.kt | 8 +--- .../org/isoron/uhabits/core/models/Habit.kt | 6 ++- .../isoron/uhabits/core/models/HabitList.kt | 8 ++-- .../isoron/uhabits/core/models/HabitType.kt | 2 - .../uhabits/core/models/ModelObservable.kt | 6 +-- .../uhabits/core/models/NumericalHabitType.kt | 2 - .../isoron/uhabits/core/models/ScoreList.kt | 8 +--- .../isoron/uhabits/core/models/WeekdayList.kt | 13 ++---- .../core/models/memory/MemoryHabitList.kt | 12 ++---- .../core/models/sqlite/records/HabitRecord.kt | 3 +- .../uhabits/core/preferences/Preferences.kt | 3 +- .../core/reminders/ReminderScheduler.kt | 25 ++--------- .../core/tasks/SingleThreadTaskRunner.kt | 4 +- .../uhabits/core/ui/NotificationTray.kt | 39 +++-------------- .../screens/habits/list/HabitCardListCache.kt | 42 ++++++++----------- .../habits/show/ShowHabitMenuPresenter.kt | 18 +++++--- .../habits/show/views/FrequencyCard.kt | 2 - .../screens/habits/show/views/TargetCard.kt | 7 ++-- .../uhabits/core/ui/views/NumberButton.kt | 27 ++++++------ .../org/isoron/uhabits/core/ui/views/Ring.kt | 3 +- .../org/isoron/platform/io/StringsTest.kt | 37 ++++++++++++++++ .../uhabits/core/models/EntryListTest.kt | 2 +- .../uhabits/core/models/ScoreListTest.kt | 7 ++-- 27 files changed, 176 insertions(+), 166 deletions(-) create mode 100644 uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Strings.kt create mode 100644 uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaStrings.kt create mode 100644 uhabits-core/src/jvmTest/java/org/isoron/platform/io/StringsTest.kt diff --git a/build.sh b/build.sh index aba44ea0..370c1a59 100755 --- a/build.sh +++ b/build.sh @@ -378,6 +378,8 @@ main() { build) shift; _parse_opts "$@" clean + log_info "Formatting code..." + gradle_run ktlintFormat || fail core_build android_build ;; diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Strings.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Strings.kt new file mode 100644 index 00000000..6c552498 --- /dev/null +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Strings.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016-2025 Álinson Santos Xavier + * + * 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 . + */ + +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 diff --git a/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaStrings.kt b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaStrings.kt new file mode 100644 index 00000000..37d3fec8 --- /dev/null +++ b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaStrings.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016-2025 Álinson Santos Xavier + * + * 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 . + */ + +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) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CommandRunner.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CommandRunner.kt index ed8cb8f2..c286a799 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CommandRunner.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CommandRunner.kt @@ -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 = LinkedList() + private val listeners: MutableList = mutableListOf() open fun run(command: Command) { taskRunner.execute( diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt index 756766ab..ee3eb48b 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt @@ -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 = HashMap() @@ -304,7 +300,7 @@ private fun truncateDate( */ fun List.groupedSum( truncateField: TruncateField, - firstWeekday: Int = Calendar.SATURDAY, + firstWeekday: Int = 7, isNumerical: Boolean ): List { val firstWeekdayEnum = DayOfWeek.values()[firstWeekday - 1] @@ -332,7 +328,7 @@ fun List.groupedSum( */ fun List.countSkippedDays( truncateField: TruncateField, - firstWeekday: Int = Calendar.SATURDAY + firstWeekday: Int = 7 ): List { val firstWeekdayEnum = DayOfWeek.values()[firstWeekday - 1] return this.map { (date, value) -> diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt index 8dac38e6..dfd68f41 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt @@ -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() diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt index d49228c2..e8e44e01 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt @@ -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 { * Removes all the habits from the list. */ open fun removeAll() { - val copy: MutableList = 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 { 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, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitType.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitType.kt index c9a4a674..b195eb6b 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitType.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitType.kt @@ -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); diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelObservable.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelObservable.kt index 9cefd5c9..3f846b8d 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelObservable.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelObservable.kt @@ -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 @@ -80,6 +76,6 @@ class ModelObservable { * Creates a new ModelObservable with no listeners. */ init { - listeners = LinkedList() + listeners = mutableListOf() } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalHabitType.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalHabitType.kt index d0150d92..1a066b6d 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalHabitType.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalHabitType.kt @@ -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); diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt index 35ca3150..01949896 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt @@ -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() + private val map = mutableMapOf() /** * 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 { - val result: MutableList = ArrayList() + val result: MutableList = mutableListOf() if (from.isNewerThan(to)) return result var current = to while (!current.isOlderThan(from)) { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt index c806ab88..731083af 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt @@ -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 = ",")}]}" diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.kt index f077db14..f9e283d2 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.kt @@ -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() + private val list = mutableListOf() @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 { - return ArrayList(list).iterator() + return list.toMutableList().iterator() } @Synchronized diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt index 6952274b..45b3b988 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt @@ -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() } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt index eb1fc11a..5db56878 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt @@ -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) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt index fcc76c27..7b101e29 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt @@ -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) } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt index 263f5f32..6b1aed1f 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunner.kt @@ -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 = LinkedList() + private val listeners: MutableList = mutableListOf() override fun addListener(listener: TaskRunner.Listener) { listeners.add(listener) } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt index 0287b807..2b6b0b7e 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt @@ -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 = HashMap() + private val active: MutableMap = 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] } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt index 43272e95..d627dc3f 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt @@ -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 = HashMap() + val idToHabit: MutableMap = mutableMapOf() val habits: MutableList - val checkmarks: HashMap - val scores: HashMap - val notes: HashMap> + val checkmarks: MutableMap + val scores: MutableMap + val notes: MutableMap> @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 = ArrayList() - val notes: MutableList = ArrayList() + val checkmarkList = mutableListOf() + val noteList = mutableListOf() 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 = data.idToHabit.keys val after: Set = newData.idToHabit.keys - val removed: MutableSet = TreeSet(before) - removed.removeAll(after) + val removed = before - after for (id in removed) remove(id!!) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt index feb3e981..e18b4bf3 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt @@ -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, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt index 354c4536..7bf21cd2 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt @@ -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, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt index b79730d8..2bf42799 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt @@ -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() + val values = mutableListOf() 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() + val targets = mutableListOf() 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() + val intervals = mutableListOf() if (habit.frequency.denominator <= 1) intervals.add(1) if (habit.frequency.denominator <= 7) intervals.add(7) intervals.add(30) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/NumberButton.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/NumberButton.kt index a89bdada..122fba73 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/NumberButton.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/NumberButton.kt @@ -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) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Ring.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Ring.kt index 50073b21..6c1d6a51 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Ring.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Ring.kt @@ -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) } } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/platform/io/StringsTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/platform/io/StringsTest.kt new file mode 100644 index 00000000..23e9c336 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/platform/io/StringsTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016-2025 Álinson Santos Xavier + * + * 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 . + */ + +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)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt index fd5a16b2..d07806f8 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt @@ -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 { diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt index 1a136f9a..f3c60603 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt @@ -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() + val values = mutableListOf() 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() + val values = mutableListOf() 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) { + private fun check(values: MutableList) { val entries = habit.originalEntries for (i in values.indices) if (values[i] == Entry.YES_MANUAL) { entries.add(