diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc344341..7b4887f77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [2.2.0] -- 2024-01-30 +### Added +- Add support for Android 14 (@iSoron, @hiqua) +- Allow user to change app language (@leondzn) + +### Fixed +- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872) +- Fix splash screen background color in dark mode (@SIKV, #1888) + +## [2.1.3] -- 2023-08-28 +### Fixed +- Use text input on Samsung devices (@iSoron, #1719) +- Prevent crash if alarm permission is revoked (@iSoron) +- Adjust widget colors (@iSoron) +- Fix bug preventing screens from updating at midnight (@iSoron) +- Fix skip button in locales that use comma instead of dot (@iSoron, #1721) + ## [2.1.2] -- 2023-05-26 ### Fixed - Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt index 7e10baa51..423e90958 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberDialog.kt @@ -2,11 +2,13 @@ package org.isoron.uhabits.activities.common.dialogs import android.app.Dialog import android.os.Bundle +import android.provider.Settings import android.text.method.DigitsKeyListener import android.view.KeyEvent import android.view.LayoutInflater import android.view.MotionEvent import android.view.View +import android.view.inputmethod.EditorInfo import androidx.appcompat.app.AppCompatDialogFragment import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.R @@ -65,7 +67,7 @@ class NumberDialog : AppCompatDialogFragment() { save() } view.skipBtnNumber.setOnClickListener { - view.value.setText((Entry.SKIP.toDouble() / 1000).toString()) + view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000))) save() } view.notes.setOnEditorActionListener { v, actionId, event -> @@ -86,6 +88,15 @@ class NumberDialog : AppCompatDialogFragment() { // https://stackoverflow.com/a/34256139 val separator = DecimalFormatSymbols.getInstance().decimalSeparator view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator") + + // https://github.com/flutter/flutter/issues/61175 + val currKeyboard = Settings.Secure.getString( + requireContext().contentResolver, + Settings.Secure.DEFAULT_INPUT_METHOD + ) + if (currKeyboard.contains("swiftkey") || currKeyboard.contains("samsung")) { + view.value.inputType = EditorInfo.TYPE_CLASS_TEXT + } } fun save() { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt index 089af4af2..047474d67 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt @@ -25,6 +25,7 @@ import android.app.AlarmManager.RTC_WAKEUP import android.app.PendingIntent import android.content.Context import android.content.Context.ALARM_SERVICE +import android.os.Build import android.util.Log import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.models.Habit @@ -56,6 +57,10 @@ class IntentScheduler ) return SchedulerResult.IGNORED } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !manager.canScheduleExactAlarms()) { + Log.e("IntentScheduler", "No permission to schedule exact alarms") + return SchedulerResult.IGNORED + } manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent) return SchedulerResult.OK } diff --git a/uhabits-android/src/main/res/layout/checkmark_popup.xml b/uhabits-android/src/main/res/layout/checkmark_popup.xml index e21c2fb54..59f5c81a1 100644 --- a/uhabits-android/src/main/res/layout/checkmark_popup.xml +++ b/uhabits-android/src/main/res/layout/checkmark_popup.xml @@ -36,7 +36,7 @@ android:layout_height="0dp" android:layout_weight="1" android:gravity="center" - android:inputType="textCapSentences" + android:inputType="textCapSentences|textMultiLine" android:textSize="@dimen/smallTextSize" android:padding="4dp" android:background="@color/transparent" diff --git a/uhabits-android/src/main/res/layout/widget_graph.xml b/uhabits-android/src/main/res/layout/widget_graph.xml index 6ee9cfb8b..6349717bd 100644 --- a/uhabits-android/src/main/res/layout/widget_graph.xml +++ b/uhabits-android/src/main/res/layout/widget_graph.xml @@ -44,6 +44,7 @@ android:layout_height="wrap_content" android:gravity="center" android:textSize="@dimen/smallTextSize" + android:maxLines="2" android:textColor="@color/white"/> diff --git a/uhabits-android/src/main/res/values-night/colors.xml b/uhabits-android/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..e98c74022 --- /dev/null +++ b/uhabits-android/src/main/res/values-night/colors.xml @@ -0,0 +1,23 @@ + + + + + @color/grey_900 + \ No newline at end of file diff --git a/uhabits-android/src/main/res/values/colors.xml b/uhabits-android/src/main/res/values/colors.xml index 9476698d5..5a59d6681 100644 --- a/uhabits-android/src/main/res/values/colors.xml +++ b/uhabits-android/src/main/res/values/colors.xml @@ -89,4 +89,5 @@ #1976D2 + @color/grey_200 \ No newline at end of file diff --git a/uhabits-android/src/main/res/values/styles.xml b/uhabits-android/src/main/res/values/styles.xml index a3b2be8ba..87d053fe9 100644 --- a/uhabits-android/src/main/res/values/styles.xml +++ b/uhabits-android/src/main/res/values/styles.xml @@ -61,6 +61,7 @@ 0.25 true @color/grey_200 + @color/color_background @color/grey_800 false @color/white diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Themes.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Themes.kt index 1b1d989e4..eef7f26d0 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Themes.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/Themes.kt @@ -125,4 +125,30 @@ class WidgetTheme : LightTheme() { override val highContrastTextColor = Color.WHITE override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50) override val lowContrastTextColor = Color.WHITE.withAlpha(0.10) + + override fun color(paletteIndex: Int): Color { + return when (paletteIndex) { + 0 -> Color(0xD32F2F) + 1 -> Color(0xE64A19) + 2 -> Color(0xF57C00) + 3 -> Color(0xFF8F00) + 4 -> Color(0xF9A825) + 5 -> Color(0xAFB42B) + 6 -> Color(0x7CB342) + 7 -> Color(0x388E3C) + 8 -> Color(0x00897B) + 9 -> Color(0x00ACC1) + 10 -> Color(0x039BE5) + 11 -> Color(0x1976D2) + 12 -> Color(0x6275f0) + 13 -> Color(0x5E35B1) + 14 -> Color(0x8E24AA) + 15 -> Color(0xD81B60) + 16 -> Color(0x5D4037) + 17 -> Color(0x757575) + 18 -> Color(0x757575) + 19 -> Color(0x9E9E9E) + else -> Color(0x000000) + } + } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt index b91b0e33a..aec974228 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt @@ -227,7 +227,7 @@ abstract class DateUtils { fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime()) @JvmStatic - fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime() + fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - applyTimezone(getLocalTime()) @JvmStatic fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday()) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt index 903099293..b63569533 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.core.utils import org.isoron.uhabits.core.AppScope +import org.isoron.uhabits.core.io.Logging import java.util.LinkedList import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService @@ -29,9 +30,10 @@ import javax.inject.Inject * A class that emits events when a new day starts. */ @AppScope -open class MidnightTimer @Inject constructor() { +open class MidnightTimer @Inject constructor(logging: Logging) { private val listeners: MutableList = LinkedList() private lateinit var executor: ScheduledExecutorService + private val logger = logging.getLogger("MidnightTimer") @Synchronized fun addListener(listener: MidnightListener) { @@ -39,7 +41,10 @@ open class MidnightTimer @Inject constructor() { } @Synchronized - fun onPause(): MutableList? = executor.shutdownNow() + fun onPause(): MutableList? { + logger.info("Pausing timer") + return executor.shutdownNow() + } @Synchronized fun onResume( @@ -47,9 +52,11 @@ open class MidnightTimer @Inject constructor() { testExecutor: ScheduledExecutorService? = null ) { executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor() + val initialDelay = DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis + logger.info("Scheduling refresh for $initialDelay ms from now") executor.scheduleAtFixedRate( { notifyListeners() }, - DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis, + initialDelay, DateUtils.DAY_LENGTH, TimeUnit.MILLISECONDS ) @@ -60,6 +67,7 @@ open class MidnightTimer @Inject constructor() { @Synchronized private fun notifyListeners() { + logger.info("Midnight refresh") for (l in listeners) { l.atMidnight() } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt index b92507da0..c0eb3fe2d 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.io.StandardLogging import org.junit.Test import java.util.Calendar import java.util.TimeZone @@ -34,7 +35,7 @@ class MidnightTimerTest : BaseUnitTest() { ) val suspendedListener = suspendCoroutine { continuation -> - MidnightTimer().apply { + MidnightTimer(StandardLogging()).apply { addListener { continuation.resume(true) } // When onResume(1, executor)