Compare commits
14 Commits
abead88ceb
...
hotfix/2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
b0a4284b66
|
|||
|
88df8d2552
|
|||
|
d4f4f8b4a9
|
|||
|
9ca1aa911a
|
|||
|
ba57ebad31
|
|||
|
8b55ffb147
|
|||
|
727e88b7b1
|
|||
|
69b5ed3a6d
|
|||
|
8b2adbf301
|
|||
|
88cc3a2a12
|
|||
|
26526a71a9
|
|||
|
11eb3713e5
|
|||
|
1df9cc7664
|
|||
|
b76da35752
|
2
.github/workflows/main.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
run: ./build.sh build
|
run: ./build.sh build
|
||||||
|
|
||||||
- name: Run Android tests
|
- name: Run Android tests
|
||||||
run: ./build.sh android-tests-parallel 23 24 25 26 28 30 31
|
run: ./build.sh android-tests-parallel 24 25 26 28 30 31
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.1.2] -- 2023-05-26
|
||||||
|
### Fixed
|
||||||
|
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
||||||
|
- Fix widget corners on Android 12 (@iSoron)
|
||||||
|
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
|
||||||
|
- Prevent soft keyboard from covering entry popup (@iSoron)
|
||||||
|
- Accept comma (instead of dot) in certain locales (@iSoron)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Remove update delay after entering a checkmark (@iSoron)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Remove stack widgets (@iSoron)
|
||||||
|
|
||||||
|
|
||||||
## [2.1.1] -- 2022-09-24
|
## [2.1.1] -- 2022-09-24
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix Tasker plugin (@iSoron, #1503)
|
- Fix Tasker plugin (@iSoron, #1503)
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ android {
|
|||||||
compileSdk = 31
|
compileSdk = 31
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode = 20102
|
versionCode = 20103
|
||||||
versionName = "2.1.2"
|
versionName = "2.1.3"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 31
|
targetSdk = 31
|
||||||
applicationId = "org.isoron.uhabits"
|
applicationId = "org.isoron.uhabits"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
@@ -36,6 +36,7 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
|
|||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
similarityCutoff = 0.00025
|
||||||
setTheme(R.style.WidgetTheme)
|
setTheme(R.style.WidgetTheme)
|
||||||
val habit = fixtures.createShortHabit()
|
val habit = fixtures.createShortHabit()
|
||||||
val computedEntries = habit.computedEntries
|
val computedEntries = habit.computedEntries
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package org.isoron.uhabits.activities.common.dialogs
|
|||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import android.text.method.DigitsKeyListener
|
import android.text.method.DigitsKeyListener
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
@@ -65,7 +67,7 @@ class NumberDialog : AppCompatDialogFragment() {
|
|||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
view.skipBtnNumber.setOnClickListener {
|
view.skipBtnNumber.setOnClickListener {
|
||||||
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
|
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
view.notes.setOnEditorActionListener { v, actionId, event ->
|
view.notes.setOnEditorActionListener { v, actionId, event ->
|
||||||
@@ -86,6 +88,15 @@ class NumberDialog : AppCompatDialogFragment() {
|
|||||||
// https://stackoverflow.com/a/34256139
|
// https://stackoverflow.com/a/34256139
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
||||||
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
|
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() {
|
fun save() {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
screen.onAttached()
|
screen.onAttached()
|
||||||
rootView.postInvalidate()
|
rootView.postInvalidate()
|
||||||
midnightTimer.onResume()
|
midnightTimer.onResume()
|
||||||
|
appComponent.reminderScheduler.scheduleAll()
|
||||||
taskRunner.run {
|
taskRunner.run {
|
||||||
try {
|
try {
|
||||||
AutoBackup(this@ListHabitsActivity).run()
|
AutoBackup(this@ListHabitsActivity).run()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.app.AlarmManager.RTC_WAKEUP
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.ALARM_SERVICE
|
import android.content.Context.ALARM_SERVICE
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
@@ -56,6 +57,10 @@ class IntentScheduler
|
|||||||
)
|
)
|
||||||
return SchedulerResult.IGNORED
|
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)
|
manager.setExactAndAllowWhileIdle(alarmType, timestamp, intent)
|
||||||
return SchedulerResult.OK
|
return SchedulerResult.OK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
|
|||||||
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
|
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.AbsListView.CHOICE_MODE_MULTIPLE
|
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
@@ -34,7 +33,6 @@ import org.isoron.uhabits.R
|
|||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
class BooleanHabitPickerDialog : HabitPickerDialog() {
|
class BooleanHabitPickerDialog : HabitPickerDialog() {
|
||||||
override fun shouldHideNumerical() = true
|
override fun shouldHideNumerical() = true
|
||||||
@@ -88,20 +86,12 @@ open class HabitPickerDialog : Activity() {
|
|||||||
with(listView) {
|
with(listView) {
|
||||||
adapter = ArrayAdapter(
|
adapter = ArrayAdapter(
|
||||||
context,
|
context,
|
||||||
android.R.layout.simple_list_item_multiple_choice,
|
android.R.layout.simple_list_item_1,
|
||||||
habitNames
|
habitNames
|
||||||
)
|
)
|
||||||
choiceMode = CHOICE_MODE_MULTIPLE
|
setOnItemClickListener { parent, view, position, id ->
|
||||||
itemsCanFocus = false
|
confirm(mutableListOf(habitIds[position]))
|
||||||
}
|
|
||||||
saveButton.setOnClickListener {
|
|
||||||
val selectedIds = mutableListOf<Long>()
|
|
||||||
for (i in 0..listView.count) {
|
|
||||||
if (listView.isItemChecked(i)) {
|
|
||||||
selectedIds.add(habitIds[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
confirm(selectedIds)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,23 +66,21 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
val res = StyledResources(context)
|
val res = StyledResources(context)
|
||||||
val bgColor: Int
|
val bgColor: Int
|
||||||
val fgColor: Int
|
val fgColor: Int
|
||||||
|
setShadowAlpha(0x4f)
|
||||||
when (entryState) {
|
when (entryState) {
|
||||||
YES_MANUAL, SKIP -> {
|
YES_MANUAL, SKIP -> {
|
||||||
bgColor = activeColor
|
bgColor = activeColor
|
||||||
fgColor = res.getColor(R.attr.contrast0)
|
fgColor = res.getColor(R.attr.contrast0)
|
||||||
setShadowAlpha(0x4f)
|
|
||||||
backgroundPaint!!.color = bgColor
|
backgroundPaint!!.color = bgColor
|
||||||
frame!!.setBackgroundDrawable(background)
|
frame!!.setBackgroundDrawable(background)
|
||||||
}
|
}
|
||||||
YES_AUTO, NO, UNKNOWN -> {
|
YES_AUTO, NO, UNKNOWN -> {
|
||||||
bgColor = res.getColor(R.attr.cardBgColor)
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
fgColor = res.getColor(R.attr.contrast60)
|
fgColor = res.getColor(R.attr.contrast60)
|
||||||
setShadowAlpha(0x00)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
bgColor = res.getColor(R.attr.cardBgColor)
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
fgColor = res.getColor(R.attr.contrast60)
|
fgColor = res.getColor(R.attr.contrast60)
|
||||||
setShadowAlpha(0x00)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ring.setPercentage(percentage)
|
ring.setPercentage(percentage)
|
||||||
@@ -126,7 +124,7 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
} else {
|
} else {
|
||||||
width = min(width, height)
|
width = min(width, height)
|
||||||
}
|
}
|
||||||
val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
|
val textSize = min(0.175f * width, getDimension(context, R.dimen.smallTextSize))
|
||||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
if (isNumerical) {
|
if (isNumerical) {
|
||||||
ring.setTextSize(textSize * 0.9f)
|
ring.setTextSize(textSize * 0.9f)
|
||||||
@@ -141,7 +139,8 @@ class CheckmarkWidgetView : HabitWidgetView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component
|
val appComponent: HabitsApplicationComponent =
|
||||||
|
(context.applicationContext as HabitsApplication).component
|
||||||
preferences = appComponent.preferences
|
preferences = appComponent.preferences
|
||||||
ring = findViewById<View>(R.id.scoreRing) as RingView
|
ring = findViewById<View>(R.id.scoreRing) as RingView
|
||||||
label = findViewById<View>(R.id.label) as TextView
|
label = findViewById<View>(R.id.label) as TextView
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
|
|||||||
val shadowRadius = dpToPixels(context, 2f).toInt()
|
val shadowRadius = dpToPixels(context, 2f).toInt()
|
||||||
val shadowOffset = dpToPixels(context, 1f).toInt()
|
val shadowOffset = dpToPixels(context, 1f).toInt()
|
||||||
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
|
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
|
||||||
val cornerRadius = dpToPixels(context, 5f)
|
val cornerRadius = dpToPixels(context, 12f)
|
||||||
val radii = FloatArray(8)
|
val radii = FloatArray(8)
|
||||||
Arrays.fill(radii, cornerRadius)
|
Arrays.fill(radii, cornerRadius)
|
||||||
val shape = RoundRectShape(radii, null, null)
|
val shape = RoundRectShape(radii, null, null)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<item android:id="@android:id/mask">
|
<item android:id="@android:id/mask">
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<corners android:radius="5dp"/>
|
<corners android:radius="12dp"/>
|
||||||
<solid android:color="?android:colorPrimary"/>
|
<solid android:color="?android:colorPrimary"/>
|
||||||
</shape>
|
</shape>
|
||||||
<color android:color="@color/white"/>
|
<color android:color="@color/white"/>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:inputType="textCapSentences"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
android:textSize="@dimen/smallTextSize"
|
android:textSize="@dimen/smallTextSize"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
|||||||
@@ -30,10 +30,4 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
</ListView>
|
</ListView>
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/buttonSave"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:text="@string/save"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="8dp"
|
||||||
tools:ignore="UselessParent">
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="@dimen/smallTextSize"
|
android:textSize="@dimen/smallTextSize"
|
||||||
|
android:maxLines="2"
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -125,4 +125,30 @@ class WidgetTheme : LightTheme() {
|
|||||||
override val highContrastTextColor = Color.WHITE
|
override val highContrastTextColor = Color.WHITE
|
||||||
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
||||||
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ abstract class DateUtils {
|
|||||||
fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime())
|
fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime())
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime()
|
fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - applyTimezone(getLocalTime())
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday())
|
fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday())
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package org.isoron.uhabits.core.utils
|
package org.isoron.uhabits.core.utils
|
||||||
|
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
|
import org.isoron.uhabits.core.io.Logging
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
@@ -29,9 +30,10 @@ import javax.inject.Inject
|
|||||||
* A class that emits events when a new day starts.
|
* A class that emits events when a new day starts.
|
||||||
*/
|
*/
|
||||||
@AppScope
|
@AppScope
|
||||||
open class MidnightTimer @Inject constructor() {
|
open class MidnightTimer @Inject constructor(logging: Logging) {
|
||||||
private val listeners: MutableList<MidnightListener> = LinkedList()
|
private val listeners: MutableList<MidnightListener> = LinkedList()
|
||||||
private lateinit var executor: ScheduledExecutorService
|
private lateinit var executor: ScheduledExecutorService
|
||||||
|
private val logger = logging.getLogger("MidnightTimer")
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addListener(listener: MidnightListener) {
|
fun addListener(listener: MidnightListener) {
|
||||||
@@ -39,7 +41,10 @@ open class MidnightTimer @Inject constructor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun onPause(): MutableList<Runnable>? = executor.shutdownNow()
|
fun onPause(): MutableList<Runnable>? {
|
||||||
|
logger.info("Pausing timer")
|
||||||
|
return executor.shutdownNow()
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun onResume(
|
fun onResume(
|
||||||
@@ -47,9 +52,11 @@ open class MidnightTimer @Inject constructor() {
|
|||||||
testExecutor: ScheduledExecutorService? = null
|
testExecutor: ScheduledExecutorService? = null
|
||||||
) {
|
) {
|
||||||
executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor()
|
executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor()
|
||||||
|
val initialDelay = DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis
|
||||||
|
logger.info("Scheduling refresh for $initialDelay ms from now")
|
||||||
executor.scheduleAtFixedRate(
|
executor.scheduleAtFixedRate(
|
||||||
{ notifyListeners() },
|
{ notifyListeners() },
|
||||||
DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis,
|
initialDelay,
|
||||||
DateUtils.DAY_LENGTH,
|
DateUtils.DAY_LENGTH,
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
)
|
)
|
||||||
@@ -60,6 +67,7 @@ open class MidnightTimer @Inject constructor() {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun notifyListeners() {
|
private fun notifyListeners() {
|
||||||
|
logger.info("Midnight refresh")
|
||||||
for (l in listeners) {
|
for (l in listeners) {
|
||||||
l.atMidnight()
|
l.atMidnight()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.isoron.uhabits.core.BaseUnitTest
|
import org.isoron.uhabits.core.BaseUnitTest
|
||||||
|
import org.isoron.uhabits.core.io.StandardLogging
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
@@ -34,7 +35,7 @@ class MidnightTimerTest : BaseUnitTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val suspendedListener = suspendCoroutine<Boolean> { continuation ->
|
val suspendedListener = suspendCoroutine<Boolean> { continuation ->
|
||||||
MidnightTimer().apply {
|
MidnightTimer(StandardLogging()).apply {
|
||||||
addListener { continuation.resume(true) }
|
addListener { continuation.resume(true) }
|
||||||
// When
|
// When
|
||||||
onResume(1, executor)
|
onResume(1, executor)
|
||||||
|
|||||||