Compare commits
39 Commits
v2.1.2
...
f074d0331d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f074d0331d | ||
|
|
f94bc62a94 | ||
| b0097fa45e | |||
|
|
15fa1fea8c | ||
| 31368cff45 | |||
| b44dd96dd3 | |||
|
|
08fab0cd8d | ||
|
|
a142685d2e | ||
| a7a95f2030 | |||
|
|
d3c90481be | ||
| f9bb0d7d7b | |||
| f23a1bedee | |||
| dcf31ba115 | |||
|
|
5409a324e8 | ||
|
|
504362e680 | ||
|
|
0ce2f8fae2 | ||
| 2fc6c67432 | |||
| 459cf02642 | |||
| 44cb64601f | |||
|
|
dd47d4cf08 | ||
|
|
8912a9d73c | ||
|
|
9bd1c6f685 | ||
|
|
e15e06034c | ||
|
|
a6180a5049 | ||
|
|
7c69b17e77 | ||
|
|
602a40a532 | ||
|
|
e00998f913 | ||
|
|
af5914c2db | ||
|
|
6c5f70638a | ||
|
|
ba88552919 | ||
|
|
8c90c4f68d | ||
|
|
08eb1a600d | ||
|
|
8ea0480d4a | ||
|
|
36ee39589e | ||
|
|
ac7a721940 | ||
|
|
b36ca8673a | ||
|
|
7cfac486f9 | ||
|
|
21dd413ab5 | ||
|
|
39e10638b5 |
@@ -5,7 +5,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
|
||||
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.2.1"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
|
||||
}
|
||||
|
||||
apply {
|
||||
|
||||
@@ -33,7 +33,7 @@ The repository will be downloaded to the directory `uhabits`.
|
||||
2. When the IDE asks you for the project location, select `uhabits` and click "Ok".
|
||||
3. Android Studio will spend some time indexing the project. When this is complete, click the toolbar icon "Sync Project with Gradle File", located near the right corner of the top toolbar.
|
||||
4. The operation will likely fail several times due to missing Android SDK components. Each time it fails, click the link "Install missing platforms", "Install build tools", etc, and try again.
|
||||
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but free to customize the device.
|
||||
5. To test the application, create a virtual Android device using the menu "Tools" and "AVD Manager". The default options should work fine, but feel free to customize the device.
|
||||
6. Click the menu "Run" and "uhabits-android". The application should launch.
|
||||
|
||||
|
||||
|
||||
@@ -86,19 +86,19 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val daggerVersion = "2.41"
|
||||
val kotlinVersion = "1.6.21"
|
||||
val kxCoroutinesVersion = "1.6.1"
|
||||
val daggerVersion = "2.43.2"
|
||||
val kotlinVersion = "1.7.10"
|
||||
val kxCoroutinesVersion = "1.6.4"
|
||||
val ktorVersion = "1.6.8"
|
||||
val espressoVersion = "3.4.0"
|
||||
|
||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
|
||||
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
|
||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
|
||||
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
|
||||
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||
androidTestImplementation("androidx.annotation:annotation:1.3.0")
|
||||
androidTestImplementation("androidx.annotation:annotation:1.4.0")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||
androidTestImplementation("androidx.test:rules:1.4.0")
|
||||
@@ -116,10 +116,10 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
implementation("androidx.appcompat:appcompat:1.4.2")
|
||||
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("com.google.android.material:material:1.5.0")
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
implementation("com.opencsv:opencsv:5.6")
|
||||
implementation(project(":uhabits-core"))
|
||||
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
|
||||
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
@@ -28,7 +28,7 @@ import org.isoron.uhabits.utils.toPaletteColor
|
||||
class ColorPickerDialog : ColorPickerDialog() {
|
||||
fun setListener(callback: OnColorPickedCallback) {
|
||||
super.setOnColorSelectedListener { c: Int ->
|
||||
val pc = c.toPaletteColor(context!!)
|
||||
val pc = c.toPaletteColor(requireContext())
|
||||
callback.onColorPicked(pc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +49,12 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
||||
private var onDateClickedListener: OnDateClickedListener? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val component = (activity!!.application as HabitsApplication).component
|
||||
val component = (requireActivity().application as HabitsApplication).component
|
||||
commandRunner = component.commandRunner
|
||||
habit = component.habitList.getById(arguments!!.getLong("habit"))!!
|
||||
habit = component.habitList.getById(requireArguments().getLong("habit"))!!
|
||||
preferences = component.preferences
|
||||
|
||||
val themeSwitcher = AndroidThemeSwitcher(activity!!, preferences)
|
||||
val themeSwitcher = AndroidThemeSwitcher(requireActivity(), preferences)
|
||||
themeSwitcher.apply()
|
||||
|
||||
chart = HistoryChart(
|
||||
@@ -69,10 +69,10 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
||||
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||
padding = 10.0,
|
||||
)
|
||||
dataView = AndroidDataView(context!!, null)
|
||||
dataView = AndroidDataView(requireContext(), null)
|
||||
dataView.view = chart!!
|
||||
|
||||
return Dialog(context!!).apply {
|
||||
return Dialog(requireContext()).apply {
|
||||
val metrics = resources.displayMetrics
|
||||
val maxHeight = resources.getDimensionPixelSize(R.dimen.history_editor_max_height)
|
||||
setContentView(dataView)
|
||||
|
||||
@@ -60,7 +60,7 @@ class WeekdayPickerDialog :
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(
|
||||
activity!!
|
||||
requireActivity()
|
||||
)
|
||||
builder
|
||||
.setTitle(R.string.select_weekdays)
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getShortWeekdayNames
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendarWithOffset
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getWeekdaySequence
|
||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getWeekdaysInMonth
|
||||
import org.isoron.uhabits.utils.ColorUtils.mixColors
|
||||
import org.isoron.uhabits.utils.StyledResources
|
||||
import org.isoron.uhabits.utils.toSimpleDataFormat
|
||||
@@ -62,7 +63,6 @@ class FrequencyChart : ScrollableChart {
|
||||
private var primaryColor = 0
|
||||
private var isBackgroundTransparent = false
|
||||
private lateinit var frequency: HashMap<Timestamp, Array<Int>>
|
||||
private var maxFreq = 0
|
||||
private var firstWeekday = Calendar.SUNDAY
|
||||
|
||||
constructor(context: Context?) : super(context) {
|
||||
@@ -82,7 +82,6 @@ class FrequencyChart : ScrollableChart {
|
||||
|
||||
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
|
||||
this.frequency = frequency
|
||||
maxFreq = getMaxFreq(frequency)
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
@@ -91,15 +90,6 @@ class FrequencyChart : ScrollableChart {
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
private fun getMaxFreq(frequency: HashMap<Timestamp, Array<Int>>): Int {
|
||||
var maxValue = 1
|
||||
for (values in frequency.values) for (value in values) maxValue = max(
|
||||
value,
|
||||
maxValue
|
||||
)
|
||||
return maxValue
|
||||
}
|
||||
|
||||
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
|
||||
this.isBackgroundTransparent = isBackgroundTransparent
|
||||
initColors()
|
||||
@@ -166,6 +156,7 @@ class FrequencyChart : ScrollableChart {
|
||||
|
||||
private fun drawColumn(canvas: Canvas, rect: RectF?, date: GregorianCalendar) {
|
||||
val values = frequency[Timestamp(date)]
|
||||
val weekDaysInMonth = getWeekdaysInMonth(Timestamp(date))
|
||||
val rowHeight = rect!!.height() / 8.0f
|
||||
prevRect!!.set(rect)
|
||||
val localeWeekdayList: Array<Int> = getWeekdaySequence(firstWeekday)
|
||||
@@ -174,7 +165,7 @@ class FrequencyChart : ScrollableChart {
|
||||
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
||||
val i = localeWeekdayList[j] % 7
|
||||
if (values != null)
|
||||
drawMarker(canvas, rect, values[i])
|
||||
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
||||
rect.offset(0f, rowHeight)
|
||||
}
|
||||
drawFooter(canvas, rect, date)
|
||||
@@ -222,7 +213,7 @@ class FrequencyChart : ScrollableChart {
|
||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
|
||||
}
|
||||
|
||||
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?) {
|
||||
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, frequency: Int) {
|
||||
// value can be negative when the entry is skipped
|
||||
val valueCopy = value?.let { max(0, it) }
|
||||
|
||||
@@ -230,7 +221,8 @@ class FrequencyChart : ScrollableChart {
|
||||
// maximal allowed mark radius
|
||||
val maxRadius = (rect.height() - 2 * padding) / 2.0f
|
||||
// the real mark radius is scaled down by a factor depending on the maximal frequency
|
||||
val scale = 1.0f / maxFreq * valueCopy!!
|
||||
|
||||
val scale = 1.0f / frequency * valueCopy!!
|
||||
val radius = maxRadius * scale
|
||||
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
|
||||
pGraph!!.color = colors[colorIndex]
|
||||
@@ -293,6 +285,5 @@ class FrequencyChart : ScrollableChart {
|
||||
frequency[Timestamp(date)] = values
|
||||
date.add(Calendar.MONTH, -1)
|
||||
}
|
||||
maxFreq = getMaxFreq(frequency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ class HabitTypeDialog : AppCompatDialogFragment() {
|
||||
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.buttonYesNo.setOnClickListener {
|
||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.YES_NO.value)
|
||||
val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.YES_NO.value)
|
||||
startActivity(intent)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.buttonMeasurable.setOnClickListener {
|
||||
val intent = IntentFactory().startEditActivity(activity!!, HabitType.NUMERICAL.value)
|
||||
val intent = IntentFactory().startEditActivity(requireActivity(), HabitType.NUMERICAL.value)
|
||||
startActivity(intent)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
val appContext = context!!.applicationContext
|
||||
val appContext = requireContext().applicationContext
|
||||
if (appContext is HabitsApplication) {
|
||||
prefs = appContext.component.preferences
|
||||
widgetUpdater = appContext.component.widgetUpdater
|
||||
@@ -91,9 +91,9 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
return true
|
||||
} else if (key == "reminderCustomize") {
|
||||
if (SDK_INT < Build.VERSION_CODES.O) return true
|
||||
createAndroidNotificationChannel(context!!)
|
||||
createAndroidNotificationChannel(requireContext())
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context!!.packageName)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID)
|
||||
startActivity(intent)
|
||||
return true
|
||||
@@ -103,7 +103,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
ringtoneManager = RingtoneManager(activity!!)
|
||||
ringtoneManager = RingtoneManager(requireActivity())
|
||||
sharedPrefs = preferenceManager.sharedPreferences
|
||||
sharedPrefs!!.registerOnSharedPreferenceChangeListener(this)
|
||||
if (!prefs.isDeveloper) {
|
||||
@@ -146,8 +146,8 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
val pref = findPreference(key)
|
||||
pref.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
activity!!.setResult(result)
|
||||
activity!!.finish()
|
||||
requireActivity().setResult(result)
|
||||
requireActivity().finish()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ kotlin {
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
compileOnly("com.google.dagger:dagger:2.41")
|
||||
compileOnly("com.google.dagger:dagger:2.43.2")
|
||||
implementation("com.google.guava:guava:31.1-android")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1")
|
||||
implementation("androidx.annotation:annotation:1.3.0")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.10")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4")
|
||||
implementation("androidx.annotation:annotation:1.4.0")
|
||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
||||
implementation("com.opencsv:opencsv:5.6")
|
||||
implementation("commons-codec:commons-codec:1.15")
|
||||
@@ -59,7 +59,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(kotlin("test-junit"))
|
||||
implementation("org.xerial:sqlite-jdbc:3.36.0.3")
|
||||
implementation("org.xerial:sqlite-jdbc:3.39.2.0")
|
||||
implementation("org.hamcrest:hamcrest:2.2")
|
||||
implementation("org.apache.commons:commons-io:1.3.2")
|
||||
implementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.isoron.uhabits.core.utils
|
||||
|
||||
import org.isoron.uhabits.core.models.Timestamp
|
||||
import java.time.YearMonth
|
||||
import java.util.Calendar
|
||||
import java.util.Calendar.DAY_OF_MONTH
|
||||
import java.util.Calendar.DAY_OF_WEEK
|
||||
@@ -178,6 +179,26 @@ abstract class DateUtils {
|
||||
return getWeekdayNames(GregorianCalendar.SHORT, firstWeekday)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a vector of Int representing the frequency of each weekday in a given month.
|
||||
*
|
||||
* @param startOfMonth a Timestamp representing the beginning of the month.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getWeekdaysInMonth(startOfMonth: Timestamp): Array<Int> {
|
||||
val month = startOfMonth.toCalendar()[Calendar.MONTH] + 1
|
||||
val year = startOfMonth.toCalendar()[Calendar.YEAR]
|
||||
val weekday = startOfMonth.weekday
|
||||
val monthLength = YearMonth.of(year, month).lengthOfMonth()
|
||||
|
||||
val freq = Array(7) { 0 }
|
||||
for (day in weekday until weekday + monthLength) {
|
||||
freq[day % 7] += 1
|
||||
}
|
||||
|
||||
return freq
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getToday(): Timestamp = Timestamp(getStartOfToday())
|
||||
|
||||
|
||||
@@ -118,6 +118,31 @@ class DateUtilsTest : BaseUnitTest() {
|
||||
assertThat(arrayOf("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"), equalTo(longWeekdayNames))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWeekdaysInMonth() {
|
||||
val february = GregorianCalendar(2018, Calendar.FEBRUARY, 1)
|
||||
val leapFebruary = GregorianCalendar(2020, Calendar.FEBRUARY, 1)
|
||||
val month = GregorianCalendar(2020, Calendar.APRIL, 1)
|
||||
val longMonth = GregorianCalendar(2020, Calendar.AUGUST, 1)
|
||||
|
||||
assertThat(
|
||||
arrayOf(4, 4, 4, 4, 4, 4, 4),
|
||||
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(february)))
|
||||
)
|
||||
assertThat(
|
||||
arrayOf(5, 4, 4, 4, 4, 4, 4),
|
||||
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(leapFebruary)))
|
||||
)
|
||||
assertThat(
|
||||
arrayOf(4, 4, 4, 4, 5, 5, 4),
|
||||
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(month)))
|
||||
)
|
||||
assertThat(
|
||||
arrayOf(5, 5, 5, 4, 4, 4, 4),
|
||||
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(longMonth)))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetToday() {
|
||||
setFixedLocalTime(FIXED_LOCAL_TIME)
|
||||
|
||||
@@ -34,7 +34,7 @@ application {
|
||||
|
||||
dependencies {
|
||||
val ktorVersion = "1.6.8"
|
||||
val kotlinVersion = "1.6.21"
|
||||
val kotlinVersion = "1.7.10"
|
||||
val logbackVersion = "1.2.11"
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||
@@ -43,9 +43,9 @@ dependencies {
|
||||
implementation("io.ktor:ktor-html-builder:$ktorVersion")
|
||||
implementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||
implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.148-kotlin-1.4.30")
|
||||
implementation("io.prometheus:simpleclient:0.15.0")
|
||||
implementation("io.prometheus:simpleclient_httpserver:0.15.0")
|
||||
implementation("io.prometheus:simpleclient_hotspot:0.15.0")
|
||||
implementation("io.prometheus:simpleclient:0.16.0")
|
||||
implementation("io.prometheus:simpleclient_httpserver:0.16.0")
|
||||
implementation("io.prometheus:simpleclient_hotspot:0.16.0")
|
||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
testImplementation(kotlin("test"))
|
||||
|
||||