Merge branch 'hotfix/2.0.3' into dev

pull/1033/head
Alinson S. Xavier 4 years ago
commit 37f03aca37
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -1,5 +1,11 @@
# Changelog # Changelog
## [2.0.3] - [Unreleased]
### Fixed
- Improve automatic checkmarks for monthly habits (@iSoron, 947)
- Fix small theme issues (@iSoron)
- Fix ANR on some Samsung phones (@iSoron, #962)
## [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")

@ -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)

@ -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
@ -59,6 +60,16 @@ import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toFormattedString import org.isoron.uhabits.utils.toFormattedString
import org.isoron.uhabits.utils.toThemedAndroidColor 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() {
private lateinit var themeSwitcher: AndroidThemeSwitcher private lateinit var themeSwitcher: AndroidThemeSwitcher
@ -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)

@ -20,14 +20,13 @@ 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.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
@ -49,7 +48,11 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
fun setState(state: SubtitleCardState) { fun setState(state: SubtitleCardState) {
val color = state.color.toThemedAndroidColor(context) val color = state.color.toThemedAndroidColor(context)
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"
}
} }

@ -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(

@ -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

@ -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))
} }
} }

@ -109,6 +109,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)
}
}
Loading…
Cancel
Save