Merge branch 'hotfix/2.0.3'

feature/sync^2^2 v2.0.3
Alinson S. Xavier 4 years ago
commit 6b9a7917b4
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -1,5 +1,15 @@
# Changelog # Changelog
## [2.0.3] - 2021-08-21
### Fixed
- Improve automatic checkmarks for monthly habits (@iSoron, #947)
- Fix small theme issues (@iSoron)
- Fix ANR on some Samsung phones (@iSoron, #962)
- Fix dates before the year 2000 (@iSoron, #967)
- Fix notification adding checkmarks to the wrong day (@hiqua, #969)
- Fix crashes in widgets (@hiqua, @iSoron, #907, #966, #965)
- Fix crash when moving habits (@hiqua, #968)
## [2.0.2] - 2021-05-23 ## [2.0.2] - 2021-05-23
### Changed ### Changed

@ -37,8 +37,8 @@ android {
compileSdkVersion(30) compileSdkVersion(30)
defaultConfig { defaultConfig {
versionCode(20002) versionCode(20003)
versionName("2.0.2") versionName("2.0.3")
minSdkVersion(23) minSdkVersion(23)
targetSdkVersion(30) targetSdkVersion(30)
applicationId("org.isoron.uhabits") applicationId("org.isoron.uhabits")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@ -23,6 +23,7 @@ import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
import org.isoron.uhabits.core.ui.views.LightTheme
import org.isoron.uhabits.utils.toFixedAndroidColor import org.isoron.uhabits.utils.toFixedAndroidColor
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -39,7 +40,12 @@ class ScoreChartTest : BaseViewTest() {
super.setUp() super.setUp()
fixtures.purgeHabits(habitList) fixtures.purgeHabits(habitList)
habit = fixtures.createLongHabit() habit = fixtures.createLongHabit()
val state = buildState(habit, prefs.firstWeekdayInt, 0) val state = buildState(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = 0,
theme = LightTheme(),
)
view = ScoreChart(targetContext).apply { view = ScoreChart(targetContext).apply {
setScores(state.scores) setScores(state.scores)
setColor(state.color.toFixedAndroidColor()) setColor(state.color.toFixedAndroidColor())
@ -72,7 +78,7 @@ class ScoreChartTest : BaseViewTest() {
@Test @Test
@Throws(Throwable::class) @Throws(Throwable::class)
fun testRender_withMonthlyBucket() { fun testRender_withMonthlyBucket() {
val (scores, bucketSize) = buildState(habit, prefs.firstWeekdayInt, 2) val (scores, bucketSize) = buildState(habit, prefs.firstWeekdayInt, 2, LightTheme())
view.setScores(scores) view.setScores(scores)
view.setBucketSize(bucketSize) view.setBucketSize(bucketSize)
view.invalidate() view.invalidate()
@ -89,7 +95,7 @@ class ScoreChartTest : BaseViewTest() {
@Test @Test
@Throws(Throwable::class) @Throws(Throwable::class)
fun testRender_withYearlyBucket() { fun testRender_withYearlyBucket() {
val state = buildState(habit, prefs.firstWeekdayInt, 4) val state = buildState(habit, prefs.firstWeekdayInt, 4, LightTheme())
view.setScores(state.scores) view.setScores(state.scores)
view.setBucketSize(state.bucketSize) view.setBucketSize(state.bucketSize)
view.invalidate() view.invalidate()

@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -43,7 +44,13 @@ class FrequencyCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.frequencyCard) as FrequencyCardView .findViewById<View>(R.id.frequencyCard) as FrequencyCardView
view.setState(FrequencyCardPresenter.buildState(habit = habit, firstWeekday = 0)) view.setState(
FrequencyCardPresenter.buildState(
habit = habit,
firstWeekday = 0,
theme = LightTheme(),
)
)
measureView(view, 800f, 600f) measureView(view, 800f, 600f)
} }

@ -26,6 +26,7 @@ import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -50,6 +51,7 @@ class OverviewCardViewTest : BaseViewTest() {
scoreYearDiff = 0.74f, scoreYearDiff = 0.74f,
totalCount = 44, totalCount = 44,
color = PaletteColor(7), color = PaletteColor(7),
theme = LightTheme(),
) )
) )
measureView(view, 800f, 300f) measureView(view, 800f, 300f)

@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter.Companion.buildState
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -43,7 +44,14 @@ class ScoreCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.scoreCard) as ScoreCardView .findViewById<View>(R.id.scoreCard) as ScoreCardView
view.setState(buildState(habit = habit, firstWeekday = 0, spinnerPosition = 0)) view.setState(
buildState(
habit = habit,
firstWeekday = 0,
spinnerPosition = 0,
theme = LightTheme(),
)
)
measureView(view, 800f, 600f) measureView(view, 800f, 600f)
} }

@ -25,6 +25,7 @@ import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -47,6 +48,7 @@ class StreakCardViewTest : BaseViewTest() {
StreakCardState( StreakCardState(
bestStreaks = habit.streaks.getBest(10), bestStreaks = habit.streaks.getBest(10),
color = habit.color, color = habit.color,
theme = LightTheme(),
) )
) )
measureView(view, 800f, 600f) measureView(view, 800f, 600f)

@ -28,6 +28,7 @@ import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList.Companion.EVERY_DAY import org.isoron.uhabits.core.models.WeekdayList.Companion.EVERY_DAY
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -54,6 +55,7 @@ class SubtitleCardViewTest : BaseViewTest() {
reminder = Reminder(8, 30, EVERY_DAY), reminder = Reminder(8, 30, EVERY_DAY),
unit = "", unit = "",
targetValue = 0.0, targetValue = 0.0,
theme = LightTheme(),
) )
) )
measureView(view, 800f, 200f) measureView(view, 800f, 200f)

@ -36,6 +36,7 @@ class AndroidCanvas : Canvas {
var innerDensity = 1.0 var innerDensity = 1.0
var innerWidth = 0 var innerWidth = 0
var innerHeight = 0 var innerHeight = 0
var mHeight = 15
var paint = Paint().apply { var paint = Paint().apply {
isAntiAlias = true isAntiAlias = true
@ -64,11 +65,10 @@ class AndroidCanvas : Canvas {
} }
override fun drawText(text: String, x: Double, y: Double) { override fun drawText(text: String, x: Double, y: Double) {
textPaint.getTextBounds(text, 0, text.length, textBounds)
innerCanvas.drawText( innerCanvas.drawText(
text, text,
x.toDp(), x.toDp(),
y.toDp() - textBounds.exactCenterY(), y.toDp() + 0.6f * mHeight,
textPaint, textPaint,
) )
} }
@ -126,10 +126,17 @@ class AndroidCanvas : Canvas {
Font.BOLD -> Typeface.DEFAULT_BOLD Font.BOLD -> Typeface.DEFAULT_BOLD
Font.FONT_AWESOME -> getFontAwesome(context) Font.FONT_AWESOME -> getFontAwesome(context)
} }
updateMHeight()
} }
override fun setFontSize(size: Double) { override fun setFontSize(size: Double) {
textPaint.textSize = size.toDp() textPaint.textSize = size.toDp()
updateMHeight()
}
private fun updateMHeight() {
textPaint.getTextBounds("m", 0, 1, textBounds)
mHeight = textBounds.height()
} }
override fun setStrokeWidth(size: Double) { override fun setStrokeWidth(size: Double) {

@ -30,6 +30,7 @@ import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.ThemeSwitcher import org.isoron.uhabits.core.ui.ThemeSwitcher
import org.isoron.uhabits.core.ui.views.DarkTheme import org.isoron.uhabits.core.ui.views.DarkTheme
import org.isoron.uhabits.core.ui.views.LightTheme import org.isoron.uhabits.core.ui.views.LightTheme
import org.isoron.uhabits.core.ui.views.PureBlackTheme
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope import org.isoron.uhabits.inject.ActivityScope
@ -66,7 +67,7 @@ constructor(
} }
override fun applyPureBlackTheme() { override fun applyPureBlackTheme() {
currentTheme = DarkTheme() currentTheme = PureBlackTheme()
context.setTheme(R.style.AppBaseThemeDark_PureBlack) context.setTheme(R.style.AppBaseThemeDark_PureBlack)
(context as Activity).window.navigationBarColor = (context as Activity).window.navigationBarColor =
ContextCompat.getColor(context, R.color.black) ContextCompat.getColor(context, R.color.black)

@ -26,6 +26,7 @@ import org.isoron.uhabits.BuildConfig
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.AboutBinding import org.isoron.uhabits.databinding.AboutBinding
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.setupToolbar import org.isoron.uhabits.utils.setupToolbar
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
@ -41,7 +42,8 @@ class AboutView(
setupToolbar( setupToolbar(
toolbar = binding.toolbar, toolbar = binding.toolbar,
color = PaletteColor(11), color = PaletteColor(11),
title = resources.getString(R.string.about) title = resources.getString(R.string.about),
theme = currentTheme(),
) )
val version = resources.getString(R.string.version_n) val version = resources.getString(R.string.version_n)
binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() } binding.tvContributors.setOnClickListener { screen.showCodeContributorsWebsite() }

@ -19,20 +19,21 @@
package org.isoron.uhabits.activities.common.dialogs package org.isoron.uhabits.activities.common.dialogs
import android.content.Context import android.content.Context
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.inject.ActivityScope import org.isoron.uhabits.inject.ActivityScope
import org.isoron.uhabits.utils.StyledResources import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.toThemedAndroidColor
import javax.inject.Inject import javax.inject.Inject
@ActivityScope @ActivityScope
class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext private val context: Context) { class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext private val context: Context) {
fun create(color: PaletteColor): ColorPickerDialog { fun create(color: PaletteColor, theme: Theme): ColorPickerDialog {
val dialog = ColorPickerDialog() val dialog = ColorPickerDialog()
val res = StyledResources(context) val res = StyledResources(context)
val androidColor = color.toThemedAndroidColor(context) val androidColor = theme.color(color).toInt()
dialog.initialize( dialog.initialize(
R.string.color_picker_default_title, R.string.color_picker_default_title,
res.getPalette(), res.getPalette(),

@ -160,26 +160,28 @@ class FrequencyPickerDialog(
private fun populateViews() { private fun populateViews() {
uncheckAll() uncheckAll()
if (freqNumerator == 1) { if (freqDenominator == 30 || freqDenominator == 31) {
if (freqDenominator == 1) { contentView.xTimesPerMonthRadioButton.isChecked = true
contentView.everyDayRadioButton.isChecked = true contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
} else { focus(contentView.xTimesPerMonthTextView)
contentView.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString())
focus(contentView.everyXDaysTextView)
}
} else { } else {
if (freqDenominator == 7) { if (freqNumerator == 1) {
contentView.xTimesPerWeekRadioButton.isChecked = true if (freqDenominator == 1) {
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString()) contentView.everyDayRadioButton.isChecked = true
focus(contentView.xTimesPerWeekTextView) } else {
} else if (freqDenominator == 30 || freqDenominator == 31) { contentView.everyXDaysRadioButton.isChecked = true
contentView.xTimesPerMonthRadioButton.isChecked = true contentView.everyXDaysTextView.setText(freqDenominator.toString())
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString()) focus(contentView.everyXDaysTextView)
focus(contentView.xTimesPerMonthTextView) }
} else { } else {
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator") if (freqDenominator == 7) {
contentView.everyDayRadioButton.isChecked = true contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerWeekTextView)
} else {
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
contentView.everyDayRadioButton.isChecked = true
}
} }
} }
} }

@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.edit
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.Html import android.text.Html
@ -39,6 +40,7 @@ import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.activities.AndroidThemeSwitcher
@ -57,7 +59,16 @@ import org.isoron.uhabits.databinding.ActivityEditHabitBinding
import org.isoron.uhabits.utils.ColorUtils import org.isoron.uhabits.utils.ColorUtils
import org.isoron.uhabits.utils.formatTime import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toFormattedString import org.isoron.uhabits.utils.toFormattedString
import org.isoron.uhabits.utils.toThemedAndroidColor
fun formatFrequency(freqNum: Int, freqDen: Int, resources: Resources) = when {
freqNum == 1 && (freqDen == 30 || freqDen == 31) -> resources.getString(R.string.every_month)
freqDen == 30 || freqDen == 31 -> resources.getString(R.string.x_times_per_month, freqNum)
freqNum == 1 && freqDen == 1 -> resources.getString(R.string.every_day)
freqNum == 1 && freqDen == 7 -> resources.getString(R.string.every_week)
freqNum == 1 && freqDen > 1 -> resources.getString(R.string.every_x_days, freqDen)
freqDen == 7 -> resources.getString(R.string.x_times_per_week, freqNum)
else -> "$freqNum/$freqDen"
}
class EditHabitActivity : AppCompatActivity() { class EditHabitActivity : AppCompatActivity() {
@ -137,7 +148,7 @@ class EditHabitActivity : AppCompatActivity() {
val colorPickerDialogFactory = ColorPickerDialogFactory(this) val colorPickerDialogFactory = ColorPickerDialogFactory(this)
binding.colorButton.setOnClickListener { binding.colorButton.setOnClickListener {
val dialog = colorPickerDialogFactory.create(color) val dialog = colorPickerDialogFactory.create(color, themeSwitcher.currentTheme)
dialog.setListener { paletteColor -> dialog.setListener { paletteColor ->
this.color = paletteColor this.color = paletteColor
updateColors() updateColors()
@ -299,14 +310,7 @@ class EditHabitActivity : AppCompatActivity() {
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
private fun populateFrequency() { private fun populateFrequency() {
binding.booleanFrequencyPicker.text = when { binding.booleanFrequencyPicker.text = formatFrequency(freqNum, freqDen, resources)
freqNum == 1 && freqDen == 1 -> getString(R.string.every_day)
freqNum == 1 && freqDen == 7 -> getString(R.string.every_week)
freqNum == 1 && freqDen > 1 -> getString(R.string.every_x_days, freqDen)
freqDen == 7 -> getString(R.string.x_times_per_week, freqNum)
freqDen == 30 || freqDen == 31 -> getString(R.string.x_times_per_month, freqNum)
else -> "$freqNum/$freqDen"
}
binding.numericalFrequencyPicker.text = when (freqDen) { binding.numericalFrequencyPicker.text = when (freqDen) {
1 -> getString(R.string.every_day) 1 -> getString(R.string.every_day)
7 -> getString(R.string.every_week) 7 -> getString(R.string.every_week)
@ -316,7 +320,7 @@ class EditHabitActivity : AppCompatActivity() {
} }
private fun updateColors() { private fun updateColors() {
androidColor = color.toThemedAndroidColor(this) androidColor = themeSwitcher.currentTheme.color(color).toInt()
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor) binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
if (!themeSwitcher.isNightMode) { if (!themeSwitcher.isNightMode) {
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f) val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)

@ -44,6 +44,7 @@ import org.isoron.uhabits.utils.addAtBottom
import org.isoron.uhabits.utils.addAtTop import org.isoron.uhabits.utils.addAtTop
import org.isoron.uhabits.utils.addBelow import org.isoron.uhabits.utils.addBelow
import org.isoron.uhabits.utils.buildToolbar import org.isoron.uhabits.utils.buildToolbar
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.dp import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.setupToolbar import org.isoron.uhabits.utils.setupToolbar
@ -93,6 +94,7 @@ class ListHabitsRootView @Inject constructor(
title = resources.getString(R.string.main_activity_title), title = resources.getString(R.string.main_activity_title),
color = PaletteColor(17), color = PaletteColor(17),
displayHomeAsUpEnabled = false, displayHomeAsUpEnabled = false,
theme = currentTheme(),
) )
addView(rootView, MATCH_PARENT, MATCH_PARENT) addView(rootView, MATCH_PARENT, MATCH_PARENT)
listAdapter.setListView(listView) listAdapter.setListView(listView)

@ -217,7 +217,7 @@ class ListHabitsScreen
} }
override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) { override fun showColorPicker(defaultColor: PaletteColor, callback: OnColorPickedCallback) {
val picker = colorPickerFactory.create(defaultColor) val picker = colorPickerFactory.create(defaultColor, themeSwitcher.currentTheme!!)
picker.setListener(callback) picker.setListener(callback)
picker.show(activity.supportFragmentManager, "picker") picker.show(activity.supportFragmentManager, "picker")
} }

@ -33,6 +33,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.views.RingView import org.isoron.uhabits.activities.common.views.RingView
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
@ -41,9 +42,9 @@ import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.dp import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.sres import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toThemedAndroidColor
import javax.inject.Inject import javax.inject.Inject
class HabitCardViewFactory class HabitCardViewFactory
@ -213,7 +214,7 @@ class HabitCardView(
fun getActiveColor(habit: Habit): Int { fun getActiveColor(habit: Habit): Int {
return when (habit.isArchived) { return when (habit.isArchived) {
true -> sres.getColor(R.attr.contrast60) true -> sres.getColor(R.attr.contrast60)
false -> habit.color.toThemedAndroidColor(context) false -> currentTheme().color(habit.color).toInt()
} }
} }

@ -35,7 +35,12 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
} }
fun setState(data: ShowHabitState) { fun setState(data: ShowHabitState) {
setupToolbar(binding.toolbar, title = data.title, color = data.color) setupToolbar(
binding.toolbar,
title = data.title,
color = data.color,
theme = data.theme,
)
binding.subtitleCard.setState(data.subtitle) binding.subtitleCard.setState(data.subtitle)
binding.overviewCard.setState(data.overview) binding.overviewCard.setState(data.overview)
binding.notesCard.setState(data.notes) binding.notesCard.setState(data.notes)

@ -24,12 +24,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.platform.time.JavaLocalDateFormatter import org.isoron.platform.time.JavaLocalDateFormatter
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
import org.isoron.uhabits.core.ui.views.BarChart import org.isoron.uhabits.core.ui.views.BarChart
import org.isoron.uhabits.databinding.ShowHabitBarBinding import org.isoron.uhabits.databinding.ShowHabitBarBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.Locale import java.util.Locale
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
@ -37,7 +37,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this) private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: BarCardState) { fun setState(state: BarCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply { binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
series = mutableListOf(state.entries.map { it.value / 1000.0 }) series = mutableListOf(state.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(state.color.paletteIndex)) colors = mutableListOf(theme.color(state.color.paletteIndex))

@ -22,16 +22,16 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this) private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: FrequencyCardState) { fun setState(state: FrequencyCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.frequencyChart.setFrequency(state.frequency) binding.frequencyChart.setFrequency(state.frequency)
binding.frequencyChart.setFirstWeekday(state.firstWeekday) binding.frequencyChart.setFirstWeekday(state.firstWeekday)
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)

@ -22,12 +22,12 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.platform.time.JavaLocalDateFormatter import org.isoron.platform.time.JavaLocalDateFormatter
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState
import org.isoron.uhabits.core.ui.views.HistoryChart import org.isoron.uhabits.core.ui.views.HistoryChart
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.Locale import java.util.Locale
class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
@ -35,7 +35,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this) private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: HistoryCardState) { fun setState(state: HistoryCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
binding.chart.view = HistoryChart( binding.chart.view = HistoryChart(
today = state.today, today = state.today,

@ -22,11 +22,11 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
import org.isoron.uhabits.databinding.ShowHabitOverviewBinding import org.isoron.uhabits.databinding.ShowHabitOverviewBinding
import org.isoron.uhabits.utils.StyledResources import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.toThemedAndroidColor
import kotlin.math.abs import kotlin.math.abs
class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
@ -42,7 +42,7 @@ class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
} }
fun setState(state: OverviewCardState) { fun setState(state: OverviewCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
val res = StyledResources(context) val res = StyledResources(context)
val inactiveColor = res.getColor(R.attr.contrast60) val inactiveColor = res.getColor(R.attr.contrast60)
binding.monthDiffLabel.setTextColor(if (state.scoreMonthDiff >= 0) androidColor else inactiveColor) binding.monthDiffLabel.setTextColor(if (state.scoreMonthDiff >= 0) androidColor else inactiveColor)

@ -24,16 +24,16 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
import org.isoron.uhabits.databinding.ShowHabitScoreBinding import org.isoron.uhabits.databinding.ShowHabitScoreBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this) private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: ScoreCardState) { fun setState(state: ScoreCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
binding.spinner.setSelection(state.spinnerPosition) binding.spinner.setSelection(state.spinnerPosition)
binding.scoreView.setScores(state.scores) binding.scoreView.setScores(state.scores)

@ -22,16 +22,16 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
import org.isoron.uhabits.databinding.ShowHabitStreakBinding import org.isoron.uhabits.databinding.ShowHabitStreakBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class StreakCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class StreakCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitStreakBinding.inflate(LayoutInflater.from(context), this) private val binding = ShowHabitStreakBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: StreakCardState) { fun setState(state: StreakCardState) {
val color = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.title.setTextColor(color) binding.title.setTextColor(androidColor)
binding.streakChart.setColor(color) binding.streakChart.setColor(androidColor)
binding.streakChart.setStreaks(state.bestStreaks) binding.streakChart.setStreaks(state.bestStreaks)
postInvalidate() postInvalidate()
} }

@ -20,19 +20,18 @@ package org.isoron.uhabits.activities.habits.show.views
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.edit.formatFrequency
import org.isoron.uhabits.activities.habits.list.views.toShortString import org.isoron.uhabits.activities.habits.list.views.toShortString
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
import org.isoron.uhabits.utils.InterfaceUtils import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.formatTime import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toThemedAndroidColor
class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
@ -47,9 +46,13 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun setState(state: SubtitleCardState) { fun setState(state: SubtitleCardState) {
val color = state.color.toThemedAndroidColor(context) val color = state.theme.color(state.color).toInt()
val reminder = state.reminder val reminder = state.reminder
binding.frequencyLabel.text = state.frequency.format(resources) binding.frequencyLabel.text = formatFrequency(
state.frequency.numerator,
state.frequency.denominator,
resources,
)
binding.questionLabel.setTextColor(color) binding.questionLabel.setTextColor(color)
binding.questionLabel.text = state.question binding.questionLabel.text = state.question
binding.reminderLabel.text = if (reminder != null) { binding.reminderLabel.text = if (reminder != null) {
@ -72,32 +75,4 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
postInvalidate() postInvalidate()
} }
@SuppressLint("StringFormatMatches")
private fun Frequency.format(resources: Resources): String {
val num = this.numerator
val den = this.denominator
if (num == den) {
return resources.getString(R.string.every_day)
}
if (den == 7) {
return resources.getString(R.string.x_times_per_week, num)
}
if (den == 30 || den == 31) {
return resources.getString(R.string.x_times_per_month, num)
}
if (num == 1) {
if (den == 7) {
return resources.getString(R.string.every_week)
}
if (den % 7 == 0) {
return resources.getString(R.string.every_x_weeks, den / 7)
}
if (den == 30 || den == 31) {
return resources.getString(R.string.every_month)
}
return resources.getString(R.string.every_x_days, den)
}
return "$num/$den"
}
} }

@ -23,15 +23,15 @@ import android.content.res.Resources
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
import org.isoron.uhabits.databinding.ShowHabitTargetBinding import org.isoron.uhabits.databinding.ShowHabitTargetBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class TargetCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class TargetCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitTargetBinding.inflate(LayoutInflater.from(context), this) private val binding = ShowHabitTargetBinding.inflate(LayoutInflater.from(context), this)
fun setState(state: TargetCardState) { fun setState(state: TargetCardState) {
val androidColor = state.color.toThemedAndroidColor(context) val androidColor = state.theme.color(state.color).toInt()
binding.targetChart.setValues(state.values) binding.targetChart.setValues(state.values)
binding.targetChart.setTargets(state.targets) binding.targetChart.setTargets(state.targets)
binding.targetChart.setLabels(state.intervals.map { intervalToLabel(resources, it) }) binding.targetChart.setLabels(state.intervals.map { intervalToLabel(resources, it) })

@ -32,13 +32,15 @@ class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val component = (application as HabitsApplication).component val component = (application as HabitsApplication).component
AndroidThemeSwitcher(this, component.preferences).apply() val themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
themeSwitcher.apply()
val binding = SettingsActivityBinding.inflate(LayoutInflater.from(this)) val binding = SettingsActivityBinding.inflate(LayoutInflater.from(this))
binding.root.setupToolbar( binding.root.setupToolbar(
toolbar = binding.toolbar, toolbar = binding.toolbar,
title = resources.getString(R.string.settings), title = resources.getString(R.string.settings),
color = PaletteColor(11), color = PaletteColor(11),
theme = themeSwitcher.currentTheme,
) )
setContentView(binding.root) setContentView(binding.root)
} }

@ -33,6 +33,7 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.AutomationBinding import org.isoron.uhabits.databinding.AutomationBinding
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.setupToolbar import org.isoron.uhabits.utils.setupToolbar
import java.util.LinkedList import java.util.LinkedList
@ -53,6 +54,7 @@ class EditSettingRootView(
title = resources.getString(R.string.app_name), title = resources.getString(R.string.app_name),
color = PaletteColor(11), color = PaletteColor(11),
displayHomeAsUpEnabled = false, displayHomeAsUpEnabled = false,
theme = currentTheme(),
) )
populateHabitSpinner() populateHabitSpinner()
binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.habitSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {

@ -63,13 +63,14 @@ class PendingIntentFactory
FLAG_UPDATE_CURRENT FLAG_UPDATE_CURRENT
) )
fun removeRepetition(habit: Habit): PendingIntent = fun removeRepetition(habit: Habit, timestamp: Timestamp?): PendingIntent =
getBroadcast( getBroadcast(
context, context,
3, 3,
Intent(context, WidgetReceiver::class.java).apply { Intent(context, WidgetReceiver::class.java).apply {
action = WidgetReceiver.ACTION_REMOVE_REPETITION action = WidgetReceiver.ACTION_REMOVE_REPETITION
data = Uri.parse(habit.uriString) data = Uri.parse(habit.uriString)
if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
}, },
FLAG_UPDATE_CURRENT FLAG_UPDATE_CURRENT
) )

@ -107,7 +107,7 @@ class AndroidNotificationTray
val removeRepetitionAction = Action( val removeRepetitionAction = Action(
R.drawable.ic_action_cancel, R.drawable.ic_action_cancel,
context.getString(R.string.no), context.getString(R.string.no),
pendingIntents.removeRepetition(habit) pendingIntents.removeRepetition(habit, timestamp)
) )
val enterAction = Action( val enterAction = Action(

@ -28,6 +28,7 @@ import android.widget.AdapterView.OnItemClickListener
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.android.datetimepicker.time.RadialPickerLayout import com.android.datetimepicker.time.RadialPickerLayout
import com.android.datetimepicker.time.TimePickerDialog import com.android.datetimepicker.time.TimePickerDialog
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.activities.AndroidThemeSwitcher
@ -35,14 +36,13 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_LIGHT import org.isoron.uhabits.core.ui.ThemeSwitcher.Companion.THEME_LIGHT
import org.isoron.uhabits.receivers.ReminderController import org.isoron.uhabits.receivers.ReminderController
import org.isoron.uhabits.utils.SystemUtils import org.isoron.uhabits.utils.SystemUtils
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.Calendar import java.util.Calendar
class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener { class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
private var habit: Habit? = null private var habit: Habit? = null
private var reminderController: ReminderController? = null private var reminderController: ReminderController? = null
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private var color: Int = 0 private var androidColor: Int = 0
override fun onCreate(bundle: Bundle?) { override fun onCreate(bundle: Bundle?) {
super.onCreate(bundle) super.onCreate(bundle)
@ -63,7 +63,7 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
habit = appComponent.habitList.getById(ContentUris.parseId(data)) habit = appComponent.habitList.getById(ContentUris.parseId(data))
} }
if (habit == null) finish() if (habit == null) finish()
color = habit!!.color.toThemedAndroidColor(this) androidColor = themeSwitcher.currentTheme.color(habit!!.color).toInt()
reminderController = appComponent.reminderController reminderController = appComponent.reminderController
dialog = AlertDialog.Builder(this) dialog = AlertDialog.Builder(this)
.setTitle(R.string.select_snooze_delay) .setTitle(R.string.select_snooze_delay)
@ -85,7 +85,7 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
calendar[Calendar.HOUR_OF_DAY], calendar[Calendar.HOUR_OF_DAY],
calendar[Calendar.MINUTE], calendar[Calendar.MINUTE],
DateFormat.is24HourFormat(this), DateFormat.is24HourFormat(this),
color androidColor
) )
dialog.show(supportFragmentManager, "timePicker") dialog.show(supportFragmentManager, "timePicker")
} }

@ -21,7 +21,6 @@ package org.isoron.uhabits.utils
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.util.Log
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
object PaletteUtils { object PaletteUtils {
@ -29,16 +28,6 @@ object PaletteUtils {
fun getAndroidTestColor(index: Int) = PaletteColor(index).toFixedAndroidColor() fun getAndroidTestColor(index: Int) = PaletteColor(index).toFixedAndroidColor()
} }
fun PaletteColor.toThemedAndroidColor(context: Context): Int {
val palette = StyledResources(context).getPalette()
return if (paletteIndex in palette.indices) {
palette[paletteIndex]
} else {
Log.w("ColorHelper", "Invalid color: $paletteIndex. Returning default.")
palette[0]
}
}
fun PaletteColor.toFixedAndroidColor(): Int { fun PaletteColor.toFixedAndroidColor(): Int {
return intArrayOf( return intArrayOf(
Color.parseColor("#D32F2F"), // 0 red Color.parseColor("#D32F2F"), // 0 red

@ -40,8 +40,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.views.Theme
import java.io.File import java.io.File
fun RelativeLayout.addBelow( fun RelativeLayout.addBelow(
@ -157,6 +161,7 @@ fun View.setupToolbar(
toolbar: Toolbar, toolbar: Toolbar,
title: String, title: String,
color: PaletteColor, color: PaletteColor,
theme: Theme,
displayHomeAsUpEnabled: Boolean = true, displayHomeAsUpEnabled: Boolean = true,
) { ) {
toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f) toolbar.elevation = InterfaceUtils.dpToPixels(context, 2f)
@ -165,7 +170,7 @@ fun View.setupToolbar(
val toolbarColor = if (!res.getBoolean(R.attr.useHabitColorAsPrimary)) { val toolbarColor = if (!res.getBoolean(R.attr.useHabitColorAsPrimary)) {
StyledResources(context).getColor(R.attr.colorPrimary) StyledResources(context).getColor(R.attr.colorPrimary)
} else { } else {
color.toThemedAndroidColor(context) theme.color(color).toInt()
} }
val darkerColor = ColorUtils.mixColors(toolbarColor, Color.BLACK, 0.75f) val darkerColor = ColorUtils.mixColors(toolbarColor, Color.BLACK, 0.75f)
toolbar.background = ColorDrawable(toolbarColor) toolbar.background = ColorDrawable(toolbarColor)
@ -175,6 +180,13 @@ fun View.setupToolbar(
activity.supportActionBar?.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled) activity.supportActionBar?.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled)
} }
fun View.currentTheme(): Theme {
val component = (context.applicationContext as HabitsApplication).component
val themeSwitcher = AndroidThemeSwitcher(context, component.preferences)
themeSwitcher.apply()
return themeSwitcher.currentTheme
}
fun Int.toMeasureSpec(mode: Int) = fun Int.toMeasureSpec(mode: Int) =
View.MeasureSpec.makeMeasureSpec(this, mode) View.MeasureSpec.makeMeasureSpec(this, mode)

@ -32,6 +32,7 @@ import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.preferences.WidgetPreferences
import org.isoron.uhabits.intents.PendingIntentFactory import org.isoron.uhabits.intents.PendingIntentFactory
import kotlin.math.max
abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolean) { abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolean) {
private val widgetPrefs: WidgetPreferences private val widgetPrefs: WidgetPreferences
@ -103,8 +104,8 @@ abstract class BaseWidget(val context: Context, val id: Int, val stacked: Boolea
private fun getBitmapFromView(view: View): Bitmap { private fun getBitmapFromView(view: View): Bitmap {
view.invalidate() view.invalidate()
val width = view.measuredWidth val width = max(1, view.measuredWidth)
val height = view.measuredHeight val height = max(1, view.measuredHeight)
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
view.draw(canvas) view.draw(canvas)

@ -109,7 +109,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
} }
protected fun getHabitsFromWidgetId(widgetId: Int): List<Habit> { protected fun getHabitsFromWidgetId(widgetId: Int): List<Habit> {
val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)!! val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)
val selectedHabits = ArrayList<Habit>(selectedIds.size) val selectedHabits = ArrayList<Habit>(selectedIds.size)
for (id in selectedIds) { for (id in selectedIds) {
val h = habits.getById(id) ?: throw HabitNotFoundException() val h = habits.getById(id) ?: throw HabitNotFoundException()

@ -24,10 +24,11 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.view.View import android.view.View
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.utils.toThemedAndroidColor
import org.isoron.uhabits.widgets.views.CheckmarkWidgetView import org.isoron.uhabits.widgets.views.CheckmarkWidgetView
open class CheckmarkWidget( open class CheckmarkWidget(
@ -53,7 +54,7 @@ open class CheckmarkWidget(
(widgetView as CheckmarkWidgetView).apply { (widgetView as CheckmarkWidgetView).apply {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
setBackgroundAlpha(preferedBackgroundAlpha) setBackgroundAlpha(preferedBackgroundAlpha)
activeColor = habit.color.toThemedAndroidColor(context) activeColor = WidgetTheme().color(habit.color).toInt()
name = habit.name name = habit.name
entryValue = habit.computedEntries.get(today).value entryValue = habit.computedEntries.get(today).value
if (habit.isNumerical) { if (habit.isNumerical) {

@ -22,9 +22,10 @@ package org.isoron.uhabits.widgets
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.FrequencyChart import org.isoron.uhabits.activities.common.views.FrequencyChart
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.utils.toThemedAndroidColor import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView import org.isoron.uhabits.widgets.views.GraphWidgetView
class FrequencyWidget( class FrequencyWidget(
@ -47,7 +48,7 @@ class FrequencyWidget(
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f) if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
(widgetView.dataView as FrequencyChart).apply { (widgetView.dataView as FrequencyChart).apply {
setFirstWeekday(firstWeekday) setFirstWeekday(firstWeekday)
setColor(habit.color.toThemedAndroidColor(context)) setColor(WidgetTheme().color(habit.color).toInt())
setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical)) setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
} }
} }

@ -22,10 +22,11 @@ package org.isoron.uhabits.widgets
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.ScoreChart import org.isoron.uhabits.activities.common.views.ScoreChart
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.utils.toThemedAndroidColor import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView import org.isoron.uhabits.widgets.views.GraphWidgetView
class ScoreWidget( class ScoreWidget(
@ -44,7 +45,8 @@ class ScoreWidget(
val viewModel = ScoreCardPresenter.buildState( val viewModel = ScoreCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = prefs.firstWeekdayInt, firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = prefs.scoreCardSpinnerPosition spinnerPosition = prefs.scoreCardSpinnerPosition,
theme = WidgetTheme(),
) )
val widgetView = view as GraphWidgetView val widgetView = view as GraphWidgetView
widgetView.setBackgroundAlpha(preferedBackgroundAlpha) widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
@ -52,7 +54,7 @@ class ScoreWidget(
(widgetView.dataView as ScoreChart).apply { (widgetView.dataView as ScoreChart).apply {
setIsTransparencyEnabled(true) setIsTransparencyEnabled(true)
setBucketSize(viewModel.bucketSize) setBucketSize(viewModel.bucketSize)
setColor(habit.color.toThemedAndroidColor(context)) setColor(WidgetTheme().color(habit.color).toInt())
setScores(viewModel.scores) setScores(viewModel.scores)
} }
} }

@ -53,7 +53,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
AppWidgetManager.INVALID_APPWIDGET_ID AppWidgetManager.INVALID_APPWIDGET_ID
) )
private val habitIds: LongArray private val habitIds: LongArray
private val widgetType: StackWidgetType? private val widgetType: StackWidgetType
private var remoteViews = ArrayList<RemoteViews>() private var remoteViews = ArrayList<RemoteViews>()
override fun onCreate() {} override fun onCreate() {}
override fun onDestroy() {} override fun onDestroy() {}
@ -86,27 +86,27 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
Log.i("StackRemoteViewsFactory", "getViewAt $position") Log.i("StackRemoteViewsFactory", "getViewAt $position")
return if (position < 0 || position > remoteViews.size) null else remoteViews[position] return if (0 <= position && position < remoteViews.size) remoteViews[position] else null
} }
private fun constructWidget( private fun constructWidget(
habit: Habit, habit: Habit,
prefs: Preferences prefs: Preferences
): BaseWidget { ): BaseWidget {
when (widgetType) { return when (widgetType) {
StackWidgetType.CHECKMARK -> return CheckmarkWidget(context, widgetId, habit, true) StackWidgetType.CHECKMARK -> CheckmarkWidget(context, widgetId, habit, true)
StackWidgetType.FREQUENCY -> return FrequencyWidget( StackWidgetType.FREQUENCY -> FrequencyWidget(
context, context,
widgetId, widgetId,
habit, habit,
prefs.firstWeekdayInt, prefs.firstWeekdayInt,
true true
) )
StackWidgetType.SCORE -> return ScoreWidget(context, widgetId, habit, true) StackWidgetType.SCORE -> ScoreWidget(context, widgetId, habit, true)
StackWidgetType.HISTORY -> return HistoryWidget(context, widgetId, habit, true) StackWidgetType.HISTORY -> HistoryWidget(context, widgetId, habit, true)
StackWidgetType.STREAKS -> return StreakWidget(context, widgetId, habit, true) StackWidgetType.STREAKS -> StreakWidget(context, widgetId, habit, true)
StackWidgetType.TARGET -> TargetWidget(context, widgetId, habit, true)
} }
throw IllegalStateException()
} }
override fun getLoadingView(): RemoteViews { override fun getLoadingView(): RemoteViews {
@ -157,6 +157,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
if (widgetTypeValue < 0) throw RuntimeException("invalid widget type") if (widgetTypeValue < 0) throw RuntimeException("invalid widget type")
if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null") if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null")
widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue) widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue)
?: throw RuntimeException("unknown widget type value: $widgetTypeValue")
habitIds = splitLongs(habitIdsStr) habitIds = splitLongs(habitIdsStr)
} }
} }

@ -19,61 +19,59 @@
package org.isoron.uhabits.widgets package org.isoron.uhabits.widgets
import org.isoron.uhabits.R import org.isoron.uhabits.R
import java.lang.IllegalStateException
/**
* Created by victoryu on 11/3/17.
*/
enum class StackWidgetType(val value: Int) { enum class StackWidgetType(val value: Int) {
CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget
HISTORY(3), STREAKS(4), TARGET(5); HISTORY(3), STREAKS(4), TARGET(5);
companion object { companion object {
fun getWidgetTypeFromValue(value: Int): StackWidgetType? { fun getWidgetTypeFromValue(value: Int): StackWidgetType? {
return when { return when (value) {
CHECKMARK.value == value -> CHECKMARK CHECKMARK.value -> CHECKMARK
FREQUENCY.value == value -> FREQUENCY FREQUENCY.value -> FREQUENCY
SCORE.value == value -> SCORE SCORE.value -> SCORE
HISTORY.value == value -> HISTORY HISTORY.value -> HISTORY
STREAKS.value == value -> STREAKS STREAKS.value -> STREAKS
TARGET.value == value -> TARGET TARGET.value -> TARGET
else -> null else -> null
} }
} }
fun getStackWidgetLayoutId(type: StackWidgetType?): Int { fun getStackWidgetLayoutId(type: StackWidgetType?): Int {
when (type) { return when (type) {
CHECKMARK -> return R.layout.checkmark_stackview_widget CHECKMARK -> R.layout.checkmark_stackview_widget
FREQUENCY -> return R.layout.frequency_stackview_widget FREQUENCY -> R.layout.frequency_stackview_widget
SCORE -> return R.layout.score_stackview_widget SCORE -> R.layout.score_stackview_widget
HISTORY -> return R.layout.history_stackview_widget HISTORY -> R.layout.history_stackview_widget
STREAKS -> return R.layout.streak_stackview_widget STREAKS -> R.layout.streak_stackview_widget
TARGET -> return R.layout.target_stackview_widget TARGET -> R.layout.target_stackview_widget
else -> throw IllegalStateException()
} }
return 0
} }
fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int { fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int {
when (type) { return when (type) {
CHECKMARK -> return R.id.checkmarkStackWidgetView CHECKMARK -> R.id.checkmarkStackWidgetView
FREQUENCY -> return R.id.frequencyStackWidgetView FREQUENCY -> R.id.frequencyStackWidgetView
SCORE -> return R.id.scoreStackWidgetView SCORE -> R.id.scoreStackWidgetView
HISTORY -> return R.id.historyStackWidgetView HISTORY -> R.id.historyStackWidgetView
STREAKS -> return R.id.streakStackWidgetView STREAKS -> R.id.streakStackWidgetView
TARGET -> return R.id.targetStackWidgetView TARGET -> R.id.targetStackWidgetView
else -> throw IllegalStateException()
} }
return 0
} }
fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int { fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int {
when (type) { return when (type) {
CHECKMARK -> return R.id.checkmarkStackWidgetEmptyView CHECKMARK -> R.id.checkmarkStackWidgetEmptyView
FREQUENCY -> return R.id.frequencyStackWidgetEmptyView FREQUENCY -> R.id.frequencyStackWidgetEmptyView
SCORE -> return R.id.scoreStackWidgetEmptyView SCORE -> R.id.scoreStackWidgetEmptyView
HISTORY -> return R.id.historyStackWidgetEmptyView HISTORY -> R.id.historyStackWidgetEmptyView
STREAKS -> return R.id.streakStackWidgetEmptyView STREAKS -> R.id.streakStackWidgetEmptyView
TARGET -> return R.id.targetStackWidgetEmptyView TARGET -> R.id.targetStackWidgetEmptyView
else -> throw IllegalStateException()
} }
return 0
} }
} }
} }

@ -24,9 +24,10 @@ import android.content.Context
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.StreakChart import org.isoron.uhabits.activities.common.views.StreakChart
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.utils.toThemedAndroidColor import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView import org.isoron.uhabits.widgets.views.GraphWidgetView
class StreakWidget( class StreakWidget(
@ -46,7 +47,7 @@ class StreakWidget(
widgetView.setBackgroundAlpha(preferedBackgroundAlpha) widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f) if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
(widgetView.dataView as StreakChart).apply { (widgetView.dataView as StreakChart).apply {
setColor(habit.color.toThemedAndroidColor(context)) setColor(WidgetTheme().color(habit.color).toInt())
setStreaks(habit.streaks.getBest(maxStreakCount)) setStreaks(habit.streaks.getBest(maxStreakCount))
} }
} }

@ -25,11 +25,12 @@ import android.view.View
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.TargetChart import org.isoron.uhabits.activities.common.views.TargetChart
import org.isoron.uhabits.activities.habits.show.views.TargetCardView.Companion.intervalToLabel import org.isoron.uhabits.activities.habits.show.views.TargetCardView.Companion.intervalToLabel
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
import org.isoron.uhabits.utils.toThemedAndroidColor import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView import org.isoron.uhabits.widgets.views.GraphWidgetView
class TargetWidget( class TargetWidget(
@ -49,8 +50,12 @@ class TargetWidget(
widgetView.setBackgroundAlpha(preferedBackgroundAlpha) widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f) if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
val chart = (widgetView.dataView as TargetChart) val chart = (widgetView.dataView as TargetChart)
val data = TargetCardPresenter.buildState(habit, prefs.firstWeekdayInt) val data = TargetCardPresenter.buildState(
chart.setColor(data.color.toThemedAndroidColor(context)) habit = habit,
firstWeekday = prefs.firstWeekdayInt,
theme = WidgetTheme(),
)
chart.setColor(WidgetTheme().color(habit.color).toInt())
chart.setTargets(data.targets) chart.setTargets(data.targets)
chart.setLabels(data.intervals.map { intervalToLabel(context.resources, it) }) chart.setLabels(data.intervals.map { intervalToLabel(context.resources, it) })
chart.setValues(data.values) chart.setValues(data.values)

@ -95,7 +95,7 @@ class WidgetUpdater
val modifiedWidgetIds = when (modifiedHabitId) { val modifiedWidgetIds = when (modifiedHabitId) {
null -> widgetIds.toList() null -> widgetIds.toList()
else -> widgetIds.filter { w -> else -> widgetIds.filter { w ->
widgetPrefs.getHabitIdsFromWidgetId(w)!!.contains(modifiedHabitId) widgetPrefs.getHabitIdsFromWidgetId(w).contains(modifiedHabitId)
} }
} }

@ -31,6 +31,7 @@ import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
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 import java.util.ArrayList
@ -58,6 +59,7 @@ open class HabitPickerDialog : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val component = (applicationContext as HabitsApplication).component val component = (applicationContext as HabitsApplication).component
AndroidThemeSwitcher(this, component.preferences).apply()
val habitList = component.habitList val habitList = component.habitList
widgetPreferences = component.widgetPreferences widgetPreferences = component.widgetPreferences
widgetUpdater = component.widgetUpdater widgetUpdater = component.widgetUpdater

@ -39,6 +39,7 @@ import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor
import org.isoron.uhabits.utils.StyledResources import org.isoron.uhabits.utils.StyledResources
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt
class CheckmarkWidgetView : HabitWidgetView { class CheckmarkWidgetView : HabitWidgetView {
var activeColor: Int = 0 var activeColor: Int = 0
@ -118,19 +119,25 @@ class CheckmarkWidgetView : HabitWidgetView {
get() = R.layout.widget_checkmark get() = R.layout.widget_checkmark
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec) var width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec) var height = MeasureSpec.getSize(heightMeasureSpec)
var textSize = 0.15f * height if (height >= width) {
val maxTextSize = getDimension(context, R.dimen.smallerTextSize) height = min(height, (width * 1.5).roundToInt())
textSize = min(textSize, maxTextSize) } else {
width = min(width, height)
}
val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
if (isNumerical) { if (isNumerical) {
ring.setTextSize(textSize * 0.75f) ring.setTextSize(textSize * 0.9f)
} else { } else {
ring.setTextSize(textSize) ring.setTextSize(textSize)
} }
ring.setThickness(0.15f * textSize) ring.setThickness(0.03f * width)
super.onMeasure(widthMeasureSpec, heightMeasureSpec) super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
} }
private fun init() { private fun init() {

@ -1,4 +1,4 @@
2.0.2: 2.0.3:
* Bug fixes * Bug fixes
2.0: 2.0:
* Track numeric habits (e.g. how many pages did you read?) * Track numeric habits (e.g. how many pages did you read?)

@ -31,19 +31,19 @@
android:id="@+id/scoreRing" android:id="@+id/scoreRing"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="0.9"
habit:thickness="2" habit:thickness="2"
habit:textSize="16" habit:textSize="16"
habit:enableFontAwesome="true" habit:enableFontAwesome="true"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginLeft="12dp" android:layout_marginLeft="4dp"
android:layout_marginRight="12dp"/> android:layout_marginRight="4dp"/>
<TextView <TextView
android:id="@+id/label" android:id="@+id/label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="0.1"
android:textSize="12sp" android:textSize="12sp"
android:textColor="@color/white" android:textColor="@color/white"
android:layout_marginLeft="6dp" android:layout_marginLeft="6dp"

@ -129,6 +129,7 @@
<item name="selectedBackground">@drawable/selected_box</item> <item name="selectedBackground">@drawable/selected_box</item>
<item name="textColorAlertDialogListItem">@color/grey_100</item> <item name="textColorAlertDialogListItem">@color/grey_100</item>
<item name="windowBackgroundColor">@color/black</item> <item name="windowBackgroundColor">@color/black</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.PureBlack</item>
</style> </style>
<style name="BaseDialog" parent="Theme.AppCompat.Light.Dialog"> <style name="BaseDialog" parent="Theme.AppCompat.Light.Dialog">
@ -151,6 +152,11 @@
<item name="palette">@array/darkPalette</item> <item name="palette">@array/darkPalette</item>
</style> </style>
<style name="PreferenceThemeOverlay.v14.Material.PureBlack">
<item name="android:background">@color/black</item>
</style>
<style name="WidgetTheme" parent="AppBaseThemeDark"> <style name="WidgetTheme" parent="AppBaseThemeDark">
<item name="cardBgColor">@color/grey_850</item> <item name="cardBgColor">@color/grey_850</item>
<item name="contrast0">@color/white</item> <item name="contrast0">@color/white</item>

@ -72,10 +72,20 @@ data class LocalDate(val daysSince2000: Int) {
return dayCache return dayCache
} }
val monthLength: Int
get() = when (month) {
4, 6, 9, 11 -> 30
2 -> if (isLeapYear(year)) 29 else 28
else -> 31
}
private fun updateYearMonthDayCache() { private fun updateYearMonthDayCache() {
var currYear = 2000 var currYear = 2000
var currDay = 0 var currDay = 0
if (daysSince2000 < 0) {
currYear -= 400
currDay -= 146097
}
while (true) { while (true) {
val currYearLength = if (isLeapYear(currYear)) 366 else 365 val currYearLength = if (isLeapYear(currYear)) 366 else 365
if (daysSince2000 < currDay + currYearLength) { if (daysSince2000 < currDay + currYearLength) {
@ -86,10 +96,8 @@ data class LocalDate(val daysSince2000: Int) {
currDay += currYearLength currDay += currYearLength
} }
} }
var currMonth = 1 var currMonth = 1
val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset
while (true) { while (true) {
if (daysSince2000 < currDay + monthOffset[currMonth]) { if (daysSince2000 < currDay + monthOffset[currMonth]) {
monthCache = currMonth monthCache = currMonth
@ -98,7 +106,6 @@ data class LocalDate(val daysSince2000: Int) {
currMonth++ currMonth++
} }
} }
currDay += monthOffset[currMonth - 1] currDay += monthOffset[currMonth - 1]
dayCache = daysSince2000 - currDay + 1 dayCache = daysSince2000 - currDay + 1
} }

@ -248,8 +248,17 @@ open class EntryList {
for (i in num - 1 until filtered.size) { for (i in num - 1 until filtered.size) {
val (begin, _) = filtered[i] val (begin, _) = filtered[i]
val (center, _) = filtered[i - num + 1] val (center, _) = filtered[i - num + 1]
if (begin.daysUntil(center) < den) { var size = den
val end = begin.plus(den - 1) if (den == 30 || den == 31) {
val beginDate = begin.toLocalDate()
size = if (beginDate.day == beginDate.monthLength) {
beginDate.plus(1).monthLength
} else {
beginDate.monthLength
}
}
if (begin.daysUntil(center) < size) {
val end = begin.plus(size - 1)
intervals.add(Interval(begin, center, end)) intervals.add(Interval(begin, center, end))
} }
} }

@ -258,7 +258,7 @@ open class Preferences(private val storage: Storage) {
putString(key, joinLongs(values)) putString(key, joinLongs(values))
} }
fun getLongArray(key: String, defValue: LongArray): LongArray? { fun getLongArray(key: String, defValue: LongArray): LongArray {
val string = getString(key, "") val string = getString(key, "")
return if (string.isEmpty()) defValue else splitLongs( return if (string.isEmpty()) defValue else splitLongs(
string string

@ -27,19 +27,18 @@ class WidgetPreferences @Inject constructor(private val storage: Preferences.Sto
storage.putLongArray(getHabitIdKey(widgetId), habitIds) storage.putLongArray(getHabitIdKey(widgetId), habitIds)
} }
fun getHabitIdsFromWidgetId(widgetId: Int): LongArray? { fun getHabitIdsFromWidgetId(widgetId: Int): LongArray {
var habitIds: LongArray?
val habitIdKey = getHabitIdKey(widgetId) val habitIdKey = getHabitIdKey(widgetId)
try { return try {
habitIds = storage.getLongArray(habitIdKey, longArrayOf(-1)) storage.getLongArray(habitIdKey, longArrayOf())
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
// Up to Loop 1.7.11, this preference was not an array, but a single // Up to Loop 1.7.11, this preference was not an array, but a single
// long. Trying to read the old preference causes a cast exception. // long. Trying to read the old preference causes a cast exception.
habitIds = LongArray(1) when (val habitId = storage.getLong(habitIdKey, -1)) {
habitIds[0] = storage.getLong(habitIdKey, -1) -1L -> longArrayOf()
storage.putLongArray(habitIdKey, habitIds) else -> longArrayOf(habitId)
}
} }
return habitIds
} }
fun removeWidget(id: Int) { fun removeWidget(id: Int) {

@ -23,6 +23,7 @@ import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.io.Logging
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitList.Order import org.isoron.uhabits.core.models.HabitList.Order
@ -54,8 +55,12 @@ import javax.inject.Inject
class HabitCardListCache @Inject constructor( class HabitCardListCache @Inject constructor(
private val allHabits: HabitList, private val allHabits: HabitList,
private val commandRunner: CommandRunner, private val commandRunner: CommandRunner,
taskRunner: TaskRunner taskRunner: TaskRunner,
logging: Logging,
) : CommandRunner.Listener { ) : CommandRunner.Listener {
private val logger = logging.getLogger("HabitCardListCache")
private var checkmarkCount = 0 private var checkmarkCount = 0
private var currentFetchTask: Task? = null private var currentFetchTask: Task? = null
private var listener: Listener private var listener: Listener
@ -316,8 +321,17 @@ class HabitCardListCache @Inject constructor(
toPosition: Int toPosition: Int
) { ) {
data.habits.removeAt(fromPosition) data.habits.removeAt(fromPosition)
data.habits.add(toPosition, habit)
listener.onItemMoved(fromPosition, toPosition) // Workaround for https://github.com/iSoron/uhabits/issues/968
val checkedToPosition = if (toPosition > data.habits.size) {
logger.error("performMove: $toPosition is strictly higher than ${data.habits.size}")
data.habits.size
} else {
toPosition
}
data.habits.add(checkedToPosition, habit)
listener.onItemMoved(fromPosition, checkedToPosition)
} }
@Synchronized @Synchronized

@ -57,6 +57,7 @@ data class ShowHabitState(
val frequency: FrequencyCardState, val frequency: FrequencyCardState,
val history: HistoryCardState, val history: HistoryCardState,
val bar: BarCardState, val bar: BarCardState,
val theme: Theme,
) )
class ShowHabitPresenter( class ShowHabitPresenter(
@ -94,11 +95,14 @@ class ShowHabitPresenter(
title = habit.name, title = habit.name,
color = habit.color, color = habit.color,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
theme = theme,
subtitle = SubtitleCardPresenter.buildState( subtitle = SubtitleCardPresenter.buildState(
habit = habit, habit = habit,
theme = theme,
), ),
overview = OverviewCardPresenter.buildState( overview = OverviewCardPresenter.buildState(
habit = habit, habit = habit,
theme = theme,
), ),
notes = NotesCardPresenter.buildState( notes = NotesCardPresenter.buildState(
habit = habit, habit = habit,
@ -106,18 +110,22 @@ class ShowHabitPresenter(
target = TargetCardPresenter.buildState( target = TargetCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
theme = theme,
), ),
streaks = StreakCartPresenter.buildState( streaks = StreakCartPresenter.buildState(
habit = habit, habit = habit,
theme = theme,
), ),
scores = ScoreCardPresenter.buildState( scores = ScoreCardPresenter.buildState(
spinnerPosition = preferences.scoreCardSpinnerPosition, spinnerPosition = preferences.scoreCardSpinnerPosition,
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
theme = theme,
), ),
frequency = FrequencyCardPresenter.buildState( frequency = FrequencyCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
theme = theme,
), ),
history = HistoryCardPresenter.buildState( history = HistoryCardPresenter.buildState(
habit = habit, habit = habit,

@ -22,12 +22,14 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.ui.views.Theme
import java.util.HashMap import java.util.HashMap
data class FrequencyCardState( data class FrequencyCardState(
val color: PaletteColor, val color: PaletteColor,
val firstWeekday: Int, val firstWeekday: Int,
val frequency: HashMap<Timestamp, Array<Int>>, val frequency: HashMap<Timestamp, Array<Int>>,
val theme: Theme,
) )
class FrequencyCardPresenter { class FrequencyCardPresenter {
@ -35,12 +37,14 @@ class FrequencyCardPresenter {
fun buildState( fun buildState(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
theme: Theme
) = FrequencyCardState( ) = FrequencyCardState(
color = habit.color, color = habit.color,
frequency = habit.originalEntries.computeWeekdayFrequency( frequency = habit.originalEntries.computeWeekdayFrequency(
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
), ),
firstWeekday = firstWeekday, firstWeekday = firstWeekday,
theme = theme,
) )
} }
} }

@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
data class OverviewCardState( data class OverviewCardState(
@ -30,11 +31,12 @@ data class OverviewCardState(
val scoreYearDiff: Float, val scoreYearDiff: Float,
val scoreToday: Float, val scoreToday: Float,
val totalCount: Long, val totalCount: Long,
val theme: Theme,
) )
class OverviewCardPresenter { class OverviewCardPresenter {
companion object { companion object {
fun buildState(habit: Habit): OverviewCardState { fun buildState(habit: Habit, theme: Theme): OverviewCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val lastMonth = today.minus(30) val lastMonth = today.minus(30)
val lastYear = today.minus(365) val lastYear = today.minus(365)
@ -52,6 +54,7 @@ class OverviewCardPresenter {
scoreMonthDiff = scoreToday - scoreLastMonth, scoreMonthDiff = scoreToday - scoreLastMonth,
scoreYearDiff = scoreToday - scoreLastYear, scoreYearDiff = scoreToday - scoreLastYear,
totalCount = totalCount, totalCount = totalCount,
theme = theme,
) )
} }
} }

@ -23,6 +23,7 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Score import org.isoron.uhabits.core.models.Score
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
data class ScoreCardState( data class ScoreCardState(
@ -30,6 +31,7 @@ data class ScoreCardState(
val bucketSize: Int, val bucketSize: Int,
val spinnerPosition: Int, val spinnerPosition: Int,
val color: PaletteColor, val color: PaletteColor,
val theme: Theme,
) )
class ScoreCardPresenter( class ScoreCardPresenter(
@ -53,6 +55,7 @@ class ScoreCardPresenter(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
spinnerPosition: Int, spinnerPosition: Int,
theme: Theme,
): ScoreCardState { ): ScoreCardState {
val bucketSize = BUCKET_SIZES[spinnerPosition] val bucketSize = BUCKET_SIZES[spinnerPosition]
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
@ -77,6 +80,7 @@ class ScoreCardPresenter(
scores = scores, scores = scores,
bucketSize = bucketSize, bucketSize = bucketSize,
spinnerPosition = spinnerPosition, spinnerPosition = spinnerPosition,
theme = theme,
) )
} }
} }

@ -22,18 +22,21 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Streak import org.isoron.uhabits.core.models.Streak
import org.isoron.uhabits.core.ui.views.Theme
data class StreakCardState( data class StreakCardState(
val color: PaletteColor, val color: PaletteColor,
val bestStreaks: List<Streak> val bestStreaks: List<Streak>,
val theme: Theme,
) )
class StreakCartPresenter { class StreakCartPresenter {
companion object { companion object {
fun buildState(habit: Habit): StreakCardState { fun buildState(habit: Habit, theme: Theme): StreakCardState {
return StreakCardState( return StreakCardState(
color = habit.color, color = habit.color,
bestStreaks = habit.streaks.getBest(10), bestStreaks = habit.streaks.getBest(10),
theme = theme,
) )
} }
} }

@ -23,6 +23,7 @@ import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.ui.views.Theme
data class SubtitleCardState( data class SubtitleCardState(
val color: PaletteColor, val color: PaletteColor,
@ -32,12 +33,14 @@ data class SubtitleCardState(
val reminder: Reminder?, val reminder: Reminder?,
val targetValue: Double, val targetValue: Double,
val unit: String, val unit: String,
val theme: Theme,
) )
class SubtitleCardPresenter { class SubtitleCardPresenter {
companion object { companion object {
fun buildState( fun buildState(
habit: Habit, habit: Habit,
theme: Theme,
): SubtitleCardState = SubtitleCardState( ): SubtitleCardState = SubtitleCardState(
color = habit.color, color = habit.color,
frequency = habit.frequency, frequency = habit.frequency,
@ -46,6 +49,7 @@ class SubtitleCardPresenter {
reminder = habit.reminder, reminder = habit.reminder,
targetValue = habit.targetValue, targetValue = habit.targetValue,
unit = habit.unit, unit = habit.unit,
theme = theme,
) )
} }
} }

@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import java.util.ArrayList import java.util.ArrayList
import java.util.Calendar import java.util.Calendar
@ -31,6 +32,7 @@ data class TargetCardState(
val values: List<Double> = listOf(), val values: List<Double> = listOf(),
val targets: List<Double> = listOf(), val targets: List<Double> = listOf(),
val intervals: List<Int> = listOf(), val intervals: List<Int> = listOf(),
val theme: Theme,
) )
class TargetCardPresenter { class TargetCardPresenter {
@ -38,6 +40,7 @@ class TargetCardPresenter {
fun buildState( fun buildState(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
theme: Theme,
): TargetCardState { ): TargetCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
@ -106,6 +109,7 @@ class TargetCardPresenter {
values = values, values = values,
targets = targets, targets = targets,
intervals = intervals, intervals = intervals,
theme = theme,
) )
} }
} }

@ -20,6 +20,7 @@
package org.isoron.uhabits.core.ui.views package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Color import org.isoron.platform.gui.Color
import org.isoron.uhabits.core.models.PaletteColor
abstract class Theme { abstract class Theme {
open val appBackgroundColor = Color(0xf4f4f4) open val appBackgroundColor = Color(0xf4f4f4)
@ -35,6 +36,10 @@ abstract class Theme {
open val toolbarBackgroundColor = Color(0xf4f4f4) open val toolbarBackgroundColor = Color(0xf4f4f4)
open val toolbarColor = Color(0xffffff) open val toolbarColor = Color(0xffffff)
fun color(paletteColor: PaletteColor): Color {
return color(paletteColor.paletteIndex)
}
open fun color(paletteIndex: Int): Color { open fun color(paletteIndex: Int): Color {
return when (paletteIndex) { return when (paletteIndex) {
0 -> Color(0xD32F2F) 0 -> Color(0xD32F2F)
@ -109,6 +114,12 @@ open class DarkTheme : Theme() {
} }
} }
class PureBlackTheme : DarkTheme() {
override val appBackgroundColor = Color(0x000000)
override val cardBackgroundColor = Color(0x000000)
override val lowContrastTextColor = Color(0x212121)
}
class WidgetTheme : LightTheme() { class WidgetTheme : LightTheme() {
override val cardBackgroundColor = Color.TRANSPARENT override val cardBackgroundColor = Color.TRANSPARENT
override val highContrastTextColor = Color.WHITE override val highContrastTextColor = Color.WHITE

@ -0,0 +1,34 @@
/*
* Copyright (C) 2016-2021 Á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.gui
import org.isoron.platform.time.LocalDate
import org.junit.Assert.assertEquals
import org.junit.Test
class DatesTest {
@Test
fun testDatesBefore2000() {
val date = LocalDate(-1)
assertEquals(date.day, 31)
assertEquals(date.month, 12)
assertEquals(date.year, 1999)
}
}

@ -43,7 +43,7 @@ class HabitCardListCacheTest : BaseUnitTest() {
for (i in 0..9) { for (i in 0..9) {
if (i == 3) habitList.add(fixtures.createLongHabit()) else habitList.add(fixtures.createShortHabit()) if (i == 3) habitList.add(fixtures.createLongHabit()) else habitList.add(fixtures.createShortHabit())
} }
cache = HabitCardListCache(habitList, commandRunner, taskRunner) cache = HabitCardListCache(habitList, commandRunner, taskRunner, mock())
cache.setCheckmarkCount(10) cache.setCheckmarkCount(10)
cache.refreshAllHabits() cache.refreshAllHabits()
cache.onAttached() cache.onAttached()

Loading…
Cancel
Save