Compare commits

...

28 Commits

Author SHA1 Message Date
516bf394f8 Merge branch 'release/2.3.1' 2025-08-13 21:30:11 -05:00
2816b7c3d0 Bump version to 2.3.1 and update changelog 2025-08-13 21:08:29 -05:00
a9acbd6cab HabitCardListView: Prevent duplicate inset decorations 2025-07-08 21:28:54 -05:00
e121f46b61 Confetti: Fix position in freeform and landscape modes 2025-07-08 21:17:23 -05:00
d57de9d10c Apply bottom insets to about and settings screens 2025-07-08 21:01:35 -05:00
e4348a2144 Prevent some views from being obscured by system UI
Fixes #2171
2025-07-08 20:51:21 -05:00
e608c6ea62 Trim unit labels when necessary 2025-06-28 19:27:17 -05:00
5403b6bd51 CheckmarkWidgetViewTest: Ignore non-deterministic test case 2025-06-28 18:33:47 -05:00
a6cf43dbca Reformat source code 2025-06-28 16:05:43 -05:00
074627f6e1 Disable confetti if animations are disabled globally
Fixes #2170
2025-06-28 16:04:07 -05:00
powerjungle
96e20f751f Fix(UI): X and ? symbols easier to distinguish in "pure black" dark mode
In addition to that, make "progress ring" a bit more visible.
2025-06-25 22:30:21 -05:00
0daa4f6a2f Format source code 2025-06-25 22:20:58 -05:00
035b392ece CSV export: Use formatted values, add notes & header 2025-06-25 22:12:02 -05:00
648c7277cf CSV export: Add more fields to Habits.csv 2025-06-25 21:48:31 -05:00
5006f5128b Update JVM target and toolchain to version 17 2025-06-25 21:00:24 -05:00
97b98a872d EmptyListViewTest: Ignore non-deterministic test failures 2025-06-24 22:22:14 -05:00
862a851e1c EmptyListViewTest: Remove unused imports 2025-06-24 22:13:08 -05:00
804030f5c0 EmptyListViewTest: Instantiate view within each test method 2025-06-24 22:10:36 -05:00
08ab3c22ce Merge branch 'master' into dev 2025-06-24 21:37:32 -05:00
b58f836d8e Merge branch 'release/2.3.0' into dev 2025-06-24 21:28:24 -05:00
9ed4630f9b Merge branch 'release/2.3.0' 2025-06-24 21:25:14 -05:00
70dab74528 Bump version to 2.3.0 and update changelog 2025-06-23 22:23:55 -05:00
7e5d2fa207 Update Gradle and AGP versions to 8.11.1 and 8.9.2 2025-06-23 21:48:00 -05:00
0e432fb332 HistoryWidget: Increase padding; update widget images 2025-06-23 21:47:47 -05:00
897a236501 Widgets: Update test images 2025-03-23 19:41:31 -05:00
0cccecec77 Widgets: Update test images 2025-03-23 19:27:12 -05:00
f1ed875256 Further increase widget corner radius to match current Android style 2025-03-23 17:10:39 -05:00
e82bd47aab Increase minimum widget size to 50x50 and 100x100
Some Samsung phones were allowing graph widgets to occupy 1x2 or
2x1 grid cells, leading to very small text. This commit bumps up
the minimum widget size to 100x100 to ensure they always occupy at
least 2x2 cells. Tested on Pixel 4, Pixel 7 and Samsung Galaxy S24.

Closes #2118
2025-03-23 16:52:23 -05:00
58 changed files with 389 additions and 164 deletions

View File

@@ -1,16 +1,54 @@
# Changelog
## [2.2.0] -- 2024-01-30
## [2.3.1] -- 2025-08-13
### Changed
- Add notes to exported CSV files (@iSoron)
### Fixed
- Prevent some views from being obscured by system UI (@iSoron, #2171)
- Disable confetti if animations are disabled globally (@iSoron, #2170)
- Make symbols easier to distinguish in "pure black" dark mode (powerjungle, #2136)
- Trim unit labels when necessary (@hiqua, @iSoron, #2158)
## [2.3.0] -- 2025-06-23
### Added
- Add support for Android 15 and 16 (@iSoron)
- Show confetti animation (@gokulk16, @iSoron, #1743)
- Show streaks for measurable habits (@teckwarz, #2059)
- Allow user to unset measurable habits (@leontodd, @kalina559, #1899, #2109)
### Changed
- Change background widget color for habits with implicit checks (@wobbba, #1915)
### Fixed
- Fix notification when goal type is set to maximum (@manish99verma, #1931)
- Never mark "at most" habits as completed for the day (@kalina559, #2077)
- Increase minimum widget size (@iSoron, #2118)
- Improve Gradle configuration (@jimlyas, #2108)
## [2.2.0] -- 2024-01-30
### Added
- Add support for Android 14 (@iSoron, @hiqua)
- Allow user to change app language (@leondzn)
### Fixed
- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872)
- Fix splash screen background color in dark mode (@SIKV, #1888)
## [2.1.3] -- 2023-08-28
### Fixed
- Use text input on Samsung devices (@iSoron, #1719)
- Prevent crash if alarm permission is revoked (@iSoron)
- Adjust widget colors (@iSoron)
@@ -18,7 +56,9 @@
- Fix skip button in locales that use comma instead of dot (@iSoron, #1721)
## [2.1.2] -- 2023-05-26
### Fixed
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
- Fix widget corners on Android 12 (@iSoron)
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
@@ -26,18 +66,23 @@
- Accept comma (instead of dot) in certain locales (@iSoron)
### Changed
- Remove update delay after entering a checkmark (@iSoron)
### Removed
- Remove stack widgets (@iSoron)
## [2.1.1] -- 2022-09-24
### Fixed
- Fix Tasker plugin (@iSoron, #1503)
## [2.1.0] -- 2022-09-10
### Added
- Allow user to add notes to specific dates (@vbh, #1103)
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
- Allow user to add skips to measurable habits (@kalina559, #1319)
@@ -48,10 +93,12 @@
- Add support for Android 13 themed icons (@cheeeeer, #1497)
### Removed
- Hide snooze button Android 12 notifications (@hiqua, #1226)
- Remove preference to set LED lights (@iSoron)
### Changed
- Hide failed habits along with completed ones (@hiqua, #1052)
- Cycle through all checkmark states when toggling (@iSoron)
- Add delay after toggling a habit (@hiqua, @kalina559, #1147)
@@ -60,6 +107,7 @@
- Increase target SDK to 31 (@hiqua)
### Fixed
- Fix small dialog buttons (@kalina559, #1096)
- Fix invalid CSV files (@hiqua, #1177)
- Fix small issues in calendar chart (@kalina559, #1314)
@@ -68,13 +116,16 @@
- Fix widgets not working correctly on API 33 (@iSoron, #1488)
### Refactoring & Testing
- Replace raster icons by vector assets (@kalina559)
- Remove JVM dependencies from uhabits-core module (@sgallese)
- Add various missing tests (@sgallese)
- Upgrade project dependencies (@hiqua, @sgallese)
## [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)
@@ -86,9 +137,11 @@
## [2.0.2] - 2021-05-23
### Changed
- Make checkmark widget resizable
### Fixed
- Fix crash caused by numerical habits with zero target (@iSoron, #903)
- Fix small issues with font size (@iSoron)
- Allow fractional target values (@sumanabhi, #911)
@@ -99,18 +152,22 @@
## [2.0.1] - 2021-05-09
### Added
- Make midnight delay optional and disabled by default (@hiqua)
- Add arrows to sort menu (@iSoron)
- Add arrows to sort menu (@iSoron)
### Removed
- Temporarily remove experimental device sync functionality. This feature will be re-added in
Loop 2.1.
### Changed
- Make implicit checkmarks easier to read (@iSoron)
- Update and improve list of translators (@hiqua, @iSoron)
### Fixed
- Disable transparency for stacked widgets (@hiqua)
- Fix various color issues on the dark theme (@hiqua, @iSoron)
- Fix "customize notifications" on older devices (@hiqua)
@@ -119,6 +176,7 @@
- Fix checkmark widget not rendering properly on some Samsung phones (@iSoron)
### Refactoring & Testing
- Finish conversion of the entire project to Kotlin (@hiqua, @iSoron, @MarKco)
- Automatically run large tests on GitHub Actions (@iSoron)
- Remove unused v21 resources (@hiqua)
@@ -126,6 +184,7 @@
## [2.0.0-alpha] - 2020-11-29
### Added
- Track numeric habits (@iSoron, @namnl)
- Skip days without breaking streak (@KristianTashkov)
- Sort habits by status (@hiqua)
@@ -136,15 +195,18 @@
- Export backups daily (@iSoron)
### Removed
- Drop support to devices older than Android 6.0 (API 23)
### Fixed
- Reset chart offset when switching scale (@alxmjo)
- Don't show reminders from archived habits (@KristianTashkov)
- Lapses on non-daily habits decrease the score too much (@iSoron)
- Update widgets at midnight (@KristianTashkov)
### Refactoring
- Convert files to Kotlin (@olegivo)
## [1.8.12] - 2021-01-30
@@ -169,13 +231,15 @@
## [1.8.8] - 2020-06-21
- Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably.
- Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work
more predictably.
- Fix crash when saving habit
## [1.8.0] - 2020-01-01
- New bar chart showing number of repetitions performed in each week, month, quarter or year.
- Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will no longer break your streak.
- Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will
no longer break your streak.
- Many more colors to choose from (now 20 in total).
- Ability to customize how transparent the widgets are on your home screen.
- Ability to customize the first day of the week.

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.8.0"
agp = "8.9.2"
annotation = "1.9.1"
appcompat = "1.7.0"
appintro = "6.3.1"

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -36,7 +36,7 @@ Updating gradle might fix this, so try again in the future to remove this and ru
If this doesn't produce any warning, try to remove it.
*/
kotlin {
jvmToolchain(11)
jvmToolchain(17)
}
android {
@@ -44,8 +44,8 @@ android {
compileSdk = 36
defaultConfig {
versionCode = 20200
versionName = "2.2.0"
versionCode = 20301
versionName = "2.3.1"
minSdk = 28
targetSdk = 36
applicationId = "org.isoron.uhabits"
@@ -79,11 +79,11 @@ android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_17)
sourceCompatibility(JavaVersion.VERSION_17)
}
kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
buildFeatures.viewBinding = true
lint.abortOnError = false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -22,7 +22,7 @@ package org.isoron.uhabits.activities.habits.list.views
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,23 +35,22 @@ class EmptyListViewTest : BaseViewTest() {
}
private val path = "habits/list/EmptyListView"
private val view: EmptyListView = EmptyListView(targetContext)
@Before
override fun setUp() {
super.setUp()
measureView(view, dpToPixels(200), dpToPixels(200))
}
@Test
@Ignore("non-deterministic failure")
fun testRender_done() {
val view = EmptyListView(targetContext)
view.showDone()
measureView(view, dpToPixels(200), dpToPixels(200))
assertRenders(view, "$path/done.png")
}
@Test
@Ignore("non-deterministic failure")
fun testRender_empty() {
val view = EmptyListView(targetContext)
view.showEmpty()
measureView(view, dpToPixels(200), dpToPixels(200))
assertRenders(view, "$path/empty.png")
}
}

View File

@@ -25,6 +25,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
@@ -57,6 +58,7 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
@Test
@Throws(IOException::class)
@Ignore("non-deterministic")
fun testRender_checked() {
assertRenders(view, PATH + "checked.png")
}

View File

@@ -26,6 +26,7 @@ import org.isoron.uhabits.BuildConfig
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.AboutBinding
import org.isoron.uhabits.utils.applyBottomInset
import org.isoron.uhabits.utils.applyRootViewInsets
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.setupToolbar
@@ -55,6 +56,7 @@ class AboutView(
binding.tvTranslate.setOnClickListener { screen.showTranslationWebsite() }
binding.tvVersion.setOnClickListener { screen.onPressDeveloperCountdown() }
binding.tvVersion.text = String.format(version, BuildConfig.VERSION_NAME)
binding.outerLinearLayout.applyBottomInset()
applyRootViewInsets()
}
}

View File

@@ -204,7 +204,7 @@ class RingView : View {
val res = StyledResources(context)
if (backgroundColor == null) backgroundColor = res.getColor(R.attr.cardBgColor)
if (inactiveColor == null) inactiveColor = res.getColor(R.attr.contrast100)
inactiveColor = setAlpha(inactiveColor!!, 0.1f)
inactiveColor = setAlpha(inactiveColor!!, 0.15f)
rect = RectF()
}

View File

@@ -23,6 +23,7 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy
import nl.dionsegijn.konfetti.core.Party
@@ -226,6 +227,14 @@ class ListHabitsScreen
override fun showConfetti(color: PaletteColor, x: Float, y: Float) {
if (x == 0f && y == 0f) return
if (preferences.isConfettiAnimationDisabled) return
if (Settings.Global.getFloat(
activity.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f
) == 0f
) {
return
}
val baseColor = themeSwitcher.currentTheme!!.color(color).toInt()
rootView.get().konfettiView.start(
Party(
@@ -299,30 +308,36 @@ class ListHabitsScreen
command.selected.size
)
}
is ChangeHabitColorCommand -> {
return activity.resources.getQuantityString(
R.plurals.toast_habits_changed,
command.selected.size
)
}
is CreateHabitCommand -> {
return activity.resources.getString(R.string.toast_habit_created)
}
is DeleteHabitsCommand -> {
return activity.resources.getQuantityString(
R.plurals.toast_habits_deleted,
command.selected.size
)
}
is EditHabitCommand -> {
return activity.resources.getQuantityString(R.plurals.toast_habits_changed, 1)
}
is UnarchiveHabitsCommand -> {
return activity.resources.getQuantityString(
R.plurals.toast_habits_unarchived,
command.selected.size
)
}
else -> return null
}
}
@@ -335,9 +350,11 @@ class ListHabitsScreen
adapter.refresh()
activity.showMessage(activity.resources.getString(R.string.habits_imported))
}
ImportDataTask.NOT_RECOGNIZED -> {
activity.showMessage(activity.resources.getString(R.string.file_not_recognized))
}
else -> {
activity.showMessage(activity.resources.getString(R.string.could_not_import))
}

View File

@@ -20,11 +20,14 @@
package org.isoron.uhabits.activities.habits.list.views
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
import android.os.Parcelable
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.END
@@ -57,6 +60,7 @@ class HabitCardListView(
) : RecyclerView(context, null, R.attr.scrollableRecyclerViewStyle) {
var checkmarkCount: Int = 0
private var insetDecorationsAdded: Boolean = false
var dataOffset: Int = 0
set(value) {
@@ -75,9 +79,32 @@ class HabitCardListView(
setHasFixedSize(true)
isLongClickable = true
layoutManager = LinearLayoutManager(context)
applyBottomInset()
super.setAdapter(adapter)
}
private fun applyBottomInset() {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
if (insetDecorationsAdded) return@setOnApplyWindowInsetsListener insets
insetDecorationsAdded = true
val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
addItemDecoration(object : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: State
) {
val itemCount = parent.adapter?.itemCount
if (parent.getChildAdapterPosition(view) == itemCount?.minus(1)) {
outRect.bottom = systemBarsInsets.bottom
}
}
})
insets
}
}
fun createHabitCardView(): HabitCardView {
return cardViewFactory.create()
}

View File

@@ -238,17 +238,18 @@ class HabitCardView(
private fun getAbsoluteButtonLocation(timestamp: Timestamp): PointF {
val containerLocation = IntArray(2)
this.getLocationOnScreen(containerLocation)
this.getLocationInWindow(containerLocation)
val relButtonLocation = getRelativeButtonLocation(timestamp)
val windowInsets = rootWindowInsets
val statusBarHeight = if (SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val xInset = windowInsets?.displayCutout?.safeInsetLeft ?: 0
val yInset = if (SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
windowInsets?.systemWindowInsetTop ?: 0
} else {
0
}
return PointF(
containerLocation[0].toFloat() + relButtonLocation.x,
containerLocation[1].toFloat() + relButtonLocation.y - statusBarHeight
containerLocation[0].toFloat() + relButtonLocation.x - xInset,
containerLocation[1].toFloat() + relButtonLocation.y - yInset
)
}

View File

@@ -189,16 +189,19 @@ class NumberButtonView(
textSize = dim(R.dimen.smallTextSize)
typeface = getFontAwesome()
}
value >= 0 -> {
label = value.toShortString()
typeface = BOLD_TYPEFACE
textSize = dim(R.dimen.smallTextSize)
}
preferences.areQuestionMarksEnabled -> {
label = resources.getString(R.string.fa_question)
typeface = getFontAwesome()
textSize = dim(R.dimen.smallerTextSize)
}
else -> {
label = "0"
typeface = BOLD_TYPEFACE
@@ -212,14 +215,23 @@ class NumberButtonView(
pUnit.color = activeColor
if (units.isBlank()) {
// Draw number without units
rect.set(0f, 0f, width.toFloat(), height.toFloat())
rect.offset(0f, 0.5f * em)
canvas.drawText(label, rect.centerX(), rect.centerY(), pNumber)
} else {
// Draw number
rect.set(0f, 0f, width.toFloat(), height.toFloat())
canvas.drawText(label, rect.centerX(), rect.centerY(), pNumber)
// Draw units
val maxUnitsWidth = width * 0.9f
var trimmedUnits = units
while (trimmedUnits.length > 2 && pUnit.measureText(trimmedUnits) > maxUnitsWidth) {
trimmedUnits = trimmedUnits.dropLast(2) + ""
}
rect.offset(0f, 1.3f * em)
canvas.drawText(units, rect.centerX(), rect.centerY(), pUnit)
canvas.drawText(trimmedUnits, rect.centerX(), rect.centerY(), pUnit)
}
drawNotesIndicator(canvas, color, em, notes)

View File

@@ -25,6 +25,7 @@ import android.widget.FrameLayout
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitState
import org.isoron.uhabits.databinding.ShowHabitBinding
import org.isoron.uhabits.utils.applyBottomInset
import org.isoron.uhabits.utils.applyToolbarInsets
import org.isoron.uhabits.utils.setupToolbar
@@ -57,6 +58,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
} else {
binding.targetCard.visibility = GONE
}
binding.linearLayout.applyBottomInset()
}
fun setListener(presenter: ShowHabitPresenter) {

View File

@@ -26,6 +26,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.SettingsActivityBinding
import org.isoron.uhabits.utils.applyBottomInset
import org.isoron.uhabits.utils.applyRootViewInsets
import org.isoron.uhabits.utils.setupToolbar
@@ -44,6 +45,7 @@ class SettingsActivity : AppCompatActivity() {
theme = themeSwitcher.currentTheme
)
binding.root.applyRootViewInsets()
binding.root.applyBottomInset()
setContentView(binding.root)
}
}

View File

@@ -260,6 +260,14 @@ fun View.applyRootViewInsets() {
}
}
fun View.applyBottomInset() {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(0, 0, 0, systemBarsInsets.bottom)
insets
}
}
fun View.applyToolbarInsets() {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())

View File

@@ -75,7 +75,8 @@ class HistoryWidget(
firstWeekday = prefs.firstWeekday,
series = listOf(),
defaultSquare = HistoryChart.Square.OFF,
notesIndicators = listOf()
notesIndicators = listOf(),
padding = 2.5
)
}
).apply {

View File

@@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
val shadowRadius = dpToPixels(context, 2f).toInt()
val shadowOffset = dpToPixels(context, 1f).toInt()
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
val cornerRadius = dpToPixels(context, 12f)
val cornerRadius = dpToPixels(context, 18f)
val radii = FloatArray(8)
Arrays.fill(radii, cornerRadius)
val shape = RoundRectShape(radii, null, null)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -28,7 +28,7 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="12dp"/>
<corners android:radius="18dp"/>
<solid android:color="?android:colorPrimary"/>
</shape>
<color android:color="@color/white"/>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
~
~ This file is part of Loop Habit Tracker.
@@ -19,16 +18,16 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?windowBackgroundColor"
android:fillViewport="true">
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?windowBackgroundColor"
android:fillViewport="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
app:popupTheme="?toolbarPopupTheme"
style="@style/Toolbar"/>
style="@style/Toolbar" />
<ScrollView
android:id="@+id/scrollView"
@@ -37,6 +36,7 @@
android:layout_below="@id/toolbar">
<LinearLayout
android:id="@+id/outerLinearLayout"
style="@style/CardList">
<LinearLayout
@@ -47,7 +47,7 @@
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="6dp"
android:src="@drawable/intro_icon_1"/>
android:src="@drawable/intro_icon_1" />
<TextView
android:layout_width="wrap_content"
@@ -57,14 +57,14 @@
android:textSize="16sp"
android:layout_margin="6dp"
android:textColor="?aboutScreenColor"
android:text="@string/app_name"/>
android:text="@string/app_name" />
<TextView
android:id="@+id/tvVersion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text=""/>
android:text="" />
</LinearLayout>
@@ -75,32 +75,32 @@
<TextView
style="@style/CardHeader"
android:text="@string/links"
android:textColor="?aboutScreenColor"/>
android:textColor="?aboutScreenColor" />
<TextView
android:id="@+id/tvRate"
style="@style/About.Item.Clickable"
android:text="@string/pref_rate_this_app"/>
android:text="@string/pref_rate_this_app" />
<TextView
android:id="@+id/tvFeedback"
style="@style/About.Item.Clickable"
android:text="@string/pref_send_feedback"/>
android:text="@string/pref_send_feedback" />
<TextView
android:id="@+id/tvTranslate"
style="@style/About.Item.Clickable"
android:text="@string/help_translate"/>
android:text="@string/help_translate" />
<TextView
android:id="@+id/tvSource"
style="@style/About.Item.Clickable"
android:text="@string/pref_view_source_code"/>
android:text="@string/pref_view_source_code" />
<TextView
android:id="@+id/tvPrivacy"
style="@style/About.Item.Clickable"
android:text="@string/pref_view_privacy"/>
android:id="@+id/tvPrivacy"
style="@style/About.Item.Clickable"
android:text="@string/pref_view_privacy" />
</LinearLayout>
@@ -111,7 +111,7 @@
<TextView
style="@style/CardHeader"
android:text="@string/developers"
android:textColor="?aboutScreenColor"/>
android:textColor="?aboutScreenColor" />
<!--
This file lists developers who contributed with at least 100 lines
@@ -120,31 +120,76 @@
linked at the bottom of the list. If you qualify, please feel free to
submit a pull request adding yourself here.
-->
<TextView style="@style/About.Item" android:text="Álinson S. Xavier (@iSoron)"/>
<TextView style="@style/About.Item" android:text="Quentin Hibon (@hiqua)"/>
<TextView style="@style/About.Item" android:text="Oleg Ivashchenko (@olegivo)"/>
<TextView style="@style/About.Item" android:text="Kristian Tashkov (@KristianTashkov)"/>
<TextView style="@style/About.Item" android:text="Jakub Kalinowski (@kalina559)"/>
<TextView style="@style/About.Item" android:text="Rechee Jozil (@recheej)"/>
<TextView style="@style/About.Item" android:text="Sebastian Gallese (@sgallese)"/>
<TextView style="@style/About.Item" android:text="Luboš Luňák (@llunak)"/>
<TextView style="@style/About.Item" android:text="Bindu (@vbh)"/>
<TextView style="@style/About.Item" android:text="Victor Yu (@vyu1)"/>
<TextView style="@style/About.Item" android:text="Christoph Hennemann (@chennemann)"/>
<TextView style="@style/About.Item" android:text="Денис (@sciamano)"/>
<TextView style="@style/About.Item" android:text="Joseph Tran (@JotraN)"/>
<TextView style="@style/About.Item" android:text="Nikhil (@regularcoder)"/>
<TextView style="@style/About.Item" android:text="JanetQC"/>
<TextView
android:id="@+id/tvContributors"
style="@style/About.Item.Clickable"
android:text="@string/view_all_contributors"/>
style="@style/About.Item"
android:text="Álinson S. Xavier (@iSoron)" />
<TextView
style="@style/About.Item"
android:text="Quentin Hibon (@hiqua)" />
<TextView
style="@style/About.Item"
android:text="Oleg Ivashchenko (@olegivo)" />
<TextView
style="@style/About.Item"
android:text="Kristian Tashkov (@KristianTashkov)" />
<TextView
style="@style/About.Item"
android:text="Jakub Kalinowski (@kalina559)" />
<TextView
style="@style/About.Item"
android:text="Rechee Jozil (@recheej)" />
<TextView
style="@style/About.Item"
android:text="Sebastian Gallese (@sgallese)" />
<TextView
style="@style/About.Item"
android:text="Luboš Luňák (@llunak)" />
<TextView
style="@style/About.Item"
android:text="Bindu (@vbh)" />
<TextView
style="@style/About.Item"
android:text="Victor Yu (@vyu1)" />
<TextView
style="@style/About.Item"
android:text="Christoph Hennemann (@chennemann)" />
<TextView
style="@style/About.Item"
android:text="Денис (@sciamano)" />
<TextView
style="@style/About.Item"
android:text="Joseph Tran (@JotraN)" />
<TextView
style="@style/About.Item"
android:text="Nikhil (@regularcoder)" />
<TextView
style="@style/About.Item"
android:text="JanetQC" />
<TextView
android:id="@+id/tvContributors"
style="@style/About.Item.Clickable"
android:text="@string/view_all_contributors" />
</LinearLayout>
<include layout="@layout/about_translators"/>
<include layout="@layout/about_translators" />
>
>
</LinearLayout>
</ScrollView>

View File

@@ -17,8 +17,7 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout
android:id="@+id/container"
<RelativeLayout android:id="@+id/container"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -29,7 +28,7 @@
android:id="@+id/toolbar"
style="@style/Toolbar"
app:popupTheme="?toolbarPopupTheme"
android:layout_alignParentTop="true"/>
android:layout_alignParentTop="true" />
<ScrollView
android:id="@+id/scrollView"
@@ -41,11 +40,12 @@
<LinearLayout
style="@style/CardList"
android:id="@+id/linearLayout"
android:clipToPadding="false">
<org.isoron.uhabits.activities.habits.show.views.SubtitleCardView
android:id="@+id/subtitleCard"
style="@style/ShowHabit.Subtitle"/>
style="@style/ShowHabit.Subtitle" />
<org.isoron.uhabits.activities.habits.show.views.NotesCardView
android:id="@+id/notesCard"
@@ -55,36 +55,36 @@
<org.isoron.uhabits.activities.habits.show.views.OverviewCardView
android:id="@+id/overviewCard"
style="@style/Card"
android:paddingTop="12dp"/>
android:paddingTop="12dp" />
<org.isoron.uhabits.activities.habits.show.views.TargetCardView
android:id="@+id/targetCard"
style="@style/Card"
android:paddingTop="12dp"/>
android:paddingTop="12dp" />
<org.isoron.uhabits.activities.habits.show.views.ScoreCardView
android:id="@+id/scoreCard"
style="@style/Card"
android:gravity="center"/>
android:gravity="center" />
<org.isoron.uhabits.activities.habits.show.views.BarCardView
android:id="@+id/barCard"
style="@style/Card"
android:gravity="center"/>
android:gravity="center" />
<org.isoron.uhabits.activities.habits.show.views.HistoryCardView
android:id="@+id/historyCard"
style="@style/Card"
android:gravity="center"
android:paddingBottom="0dp"/>
android:paddingBottom="0dp" />
<org.isoron.uhabits.activities.habits.show.views.StreakCardView
android:id="@+id/streakCard"
style="@style/Card"/>
style="@style/Card" />
<org.isoron.uhabits.activities.habits.show.views.FrequencyCardView
android:id="@+id/frequencyCard"
style="@style/Card"/>
style="@style/Card" />
</LinearLayout>
</ScrollView>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
~
~ This file is part of Loop Habit Tracker.
@@ -32,8 +31,7 @@
android:layout_marginBottom="8dp"
android:textColor="?attr/contrast60"
android:textSize="@dimen/regularTextSize"
tools:text="Have you worked out today?"
/>
tools:text="Have you worked out today?" />
<LinearLayout
android:layout_width="match_parent"
@@ -54,12 +52,14 @@
android:id="@+id/targetText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8.5k"
android:text=""
android:textColor="?attr/contrast60"
android:textSize="@dimen/smallTextSize"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
/>
android:maxEms="7"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/frequencyIcon"
@@ -93,7 +93,7 @@
android:layout_height="wrap_content"
android:paddingTop="1dp"
android:textColor="?attr/contrast60"
android:text="8:00 AM"
android:text=""
android:layout_marginStart="4dp"
android:textSize="@dimen/smallTextSize" />

View File

@@ -126,8 +126,9 @@
<item name="highlightedBackgroundColor">@color/black</item>
<item name="contrast0">@color/black</item>
<item name="contrast20">@color/grey_900</item>
<item name="contrast60">@color/grey_700</item>
<item name="contrast80">@color/grey_700</item>
<item name="contrast40">@color/grey_800</item>
<item name="contrast60">@color/grey_500</item>
<item name="contrast80">@color/grey_400</item>
<item name="contrast100">@color/grey_200</item>
<item name="selectedBackground">@drawable/selected_box</item>
<item name="textColorAlertDialogListItem">@color/grey_100</item>

View File

@@ -19,8 +19,8 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="40dp"
android:minWidth="40dp"
android:minHeight="50dp"
android:minWidth="50dp"
android:initialLayout="@layout/widget_wrapper"
android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_frequency"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_history"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_score"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_streaks"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_target"
android:resizeMode="vertical|horizontal"

View File

@@ -0,0 +1 @@
Date,Value,Notes
1 Date Value Notes
1 Date Value Notes

View File

@@ -1 +1,2 @@
Date,Score
2015-01-25,0.0000
1 2015-01-25 Date 0.0000 Score
1 Date Score
2 2015-01-25 2015-01-25 0.0000 0.0000

View File

@@ -1,10 +1,11 @@
2015-01-25,2
2015-01-24,0
2015-01-23,1
2015-01-22,2
2015-01-21,2
2015-01-20,2
2015-01-19,1
2015-01-18,1
2015-01-17,2
2015-01-16,2
Date,Value,Notes
2015-01-25,YES_MANUAL,
2015-01-24,NO,Sick
2015-01-23,YES_AUTO,"Forgot to do it, really"
2015-01-22,YES_MANUAL,
2015-01-21,YES_MANUAL,
2015-01-20,YES_MANUAL,
2015-01-19,YES_AUTO,"""Vacation"""
2015-01-18,YES_AUTO,
2015-01-17,YES_MANUAL,
2015-01-16,YES_MANUAL,
1 2015-01-25 Date 2 Value Notes
2 2015-01-24 2015-01-25 0 YES_MANUAL
3 2015-01-23 2015-01-24 1 NO Sick
4 2015-01-22 2015-01-23 2 YES_AUTO Forgot to do it, really
5 2015-01-21 2015-01-22 2 YES_MANUAL
6 2015-01-20 2015-01-21 2 YES_MANUAL
7 2015-01-19 2015-01-20 1 YES_MANUAL
8 2015-01-18 2015-01-19 1 YES_AUTO "Vacation"
9 2015-01-17 2015-01-18 2 YES_AUTO
10 2015-01-16 2015-01-17 2 YES_MANUAL
11 2015-01-16 YES_MANUAL

View File

@@ -1,3 +1,4 @@
Date,Score
2015-01-25,0.2557
2015-01-24,0.2226
2015-01-23,0.1991
1 2015-01-25 Date 0.2557 Score
1 Date Score
2 2015-01-25 2015-01-25 0.2557 0.2557
3 2015-01-24 2015-01-24 0.2226 0.2226
4 2015-01-23 2015-01-23 0.1991 0.1991

View File

@@ -1,11 +1,11 @@
Date,Meditate,Wake up early,
2015-01-25,-1,2,
2015-01-24,-1,0,
2015-01-23,-1,1,
2015-01-22,-1,2,
2015-01-21,-1,2,
2015-01-20,-1,2,
2015-01-19,-1,1,
2015-01-18,-1,1,
2015-01-17,-1,2,
2015-01-16,-1,2,
2015-01-25,UNKNOWN,YES_MANUAL,
2015-01-24,UNKNOWN,NO,
2015-01-23,UNKNOWN,YES_AUTO,
2015-01-22,UNKNOWN,YES_MANUAL,
2015-01-21,UNKNOWN,YES_MANUAL,
2015-01-20,UNKNOWN,YES_MANUAL,
2015-01-19,UNKNOWN,YES_AUTO,
2015-01-18,UNKNOWN,YES_AUTO,
2015-01-17,UNKNOWN,YES_MANUAL,
2015-01-16,UNKNOWN,YES_MANUAL,
1 Date Meditate Wake up early
2 2015-01-25 -1 UNKNOWN 2 YES_MANUAL
3 2015-01-24 -1 UNKNOWN 0 NO
4 2015-01-23 -1 UNKNOWN 1 YES_AUTO
5 2015-01-22 -1 UNKNOWN 2 YES_MANUAL
6 2015-01-21 -1 UNKNOWN 2 YES_MANUAL
7 2015-01-20 -1 UNKNOWN 2 YES_MANUAL
8 2015-01-19 -1 UNKNOWN 1 YES_AUTO
9 2015-01-18 -1 UNKNOWN 1 YES_AUTO
10 2015-01-17 -1 UNKNOWN 2 YES_MANUAL
11 2015-01-16 -1 UNKNOWN 2 YES_MANUAL

View File

@@ -1,3 +1,3 @@
Position,Name,Question,Description,NumRepetitions,Interval,Color
001,Meditate,Did you meditate this morning?,,1,1,#FF8F00
002,Wake up early,Did you wake up before 6am?,,2,3,#00897B
Position,Name,Type,Question,Description,FrequencyNumerator,FrequencyDenominator,Color,Unit,Target Type,Target Value,Archived?
001,Meditate,YES_NO,Did you meditate this morning?,,1,1,#FF8F00,,,,false
002,Wake up early,YES_NO,Did you wake up before 6am?,,2,3,#00897B,,,,false
1 Position Name Type Question NumRepetitions Description Interval FrequencyNumerator FrequencyDenominator Color Unit Target Type Target Value Archived?
2 001 Meditate YES_NO Did you meditate this morning? 1 1 1 1 #FF8F00 false
3 002 Wake up early YES_NO Did you wake up before 6am? 2 3 2 3 #00897B false

View File

@@ -24,7 +24,7 @@ plugins {
kotlin {
jvm().withJava()
jvmToolchain(11)
jvmToolchain(17)
sourceSets {
val commonMain by getting {

View File

@@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.core.io
import com.opencsv.CSVWriter
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.EntryList
import org.isoron.uhabits.core.models.Habit
@@ -32,7 +33,6 @@ import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException
import java.io.Writer
import java.util.ArrayList
import java.util.LinkedList
import java.util.Locale
import java.util.zip.ZipEntry
@@ -109,11 +109,14 @@ class HabitsCSVExporter(
var oldest = today
val known = habit.computedEntries.getKnown()
if (known.isNotEmpty()) oldest = known[known.size - 1].timestamp
val csv = CSVWriter(out)
csv.writeNext(arrayOf("Date", "Score"), false)
for ((timestamp1, value) in habit.scores.getByInterval(oldest, today)) {
val timestamp = dateFormat.format(timestamp1.unixTime)
val score = String.format(Locale.US, "%.4f", value)
out.write(String.format("%s,%s\n", timestamp, score))
csv.writeNext(arrayOf(timestamp, score), false)
}
csv.close()
out.close()
}
@@ -122,10 +125,20 @@ class HabitsCSVExporter(
val out = FileWriter(exportDirName + filename)
generatedFilenames.add(filename)
val dateFormat = DateFormats.getCSVDateFormat()
for ((timestamp, value) in entries.getKnown()) {
val date = dateFormat.format(timestamp.toJavaDate())
out.write(String.format(Locale.US, "%s,%d\n", date, value))
val csv = CSVWriter(out)
csv.writeNext(arrayOf("Date", "Value", "Notes"), false)
for (entry in entries.getKnown()) {
val date = dateFormat.format(entry.timestamp.toJavaDate())
csv.writeNext(
arrayOf(
date,
entry.formattedValue,
entry.notes
),
false
)
}
csv.close()
out.close()
}
@@ -167,7 +180,7 @@ class HabitsCSVExporter(
checksWriter.write(sb.toString())
scoresWriter.write(sb.toString())
for (j in selectedHabits.indices) {
checksWriter.write(checkmarks[j][i].value.toString())
checksWriter.write(checkmarks[j][i].formattedValue)
checksWriter.write(delimiter)
val score = String.format(Locale.US, "%.4f", scores[j][i].value)
scoresWriter.write(score)

View File

@@ -23,6 +23,16 @@ data class Entry(
val value: Int,
val notes: String = ""
) {
val formattedValue: String
get() = when (value) {
YES_MANUAL -> "YES_MANUAL"
YES_AUTO -> "YES_AUTO"
NO -> "NO"
SKIP -> "SKIP"
UNKNOWN -> "UNKNOWN"
else -> value.toString()
}
companion object {
/**
* Value indicating that the habit is not applicable for this timestamp.

View File

@@ -22,6 +22,7 @@ import com.opencsv.CSVWriter
import java.io.IOException
import java.io.Writer
import java.util.LinkedList
import java.util.Locale
import javax.annotation.concurrent.ThreadSafe
/**
@@ -182,24 +183,34 @@ abstract class HabitList : Iterable<Habit> {
val header = arrayOf(
"Position",
"Name",
"Type",
"Question",
"Description",
"NumRepetitions",
"Interval",
"Color"
"FrequencyNumerator",
"FrequencyDenominator",
"Color",
"Unit",
"Target Type",
"Target Value",
"Archived?"
)
val csv = CSVWriter(out)
csv.writeNext(header, false)
for (habit in this) {
val (numerator, denominator) = habit.frequency
val cols = arrayOf(
String.format("%03d", indexOf(habit) + 1),
String.format(Locale.US, "%03d", indexOf(habit) + 1),
habit.name,
habit.type.name,
habit.question,
habit.description,
numerator.toString(),
denominator.toString(),
habit.color.toCsvColor()
habit.color.toCsvColor(),
if (habit.isNumerical) habit.unit else "",
if (habit.isNumerical) habit.targetType.name else "",
if (habit.isNumerical) habit.targetValue.toString() else "",
habit.isArchived.toString()
)
csv.writeNext(cols, false)
}

View File

@@ -35,6 +35,10 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis
true, false, false, true, true, true, false, false, true, true
)
private var NON_DAILY_HABIT_NOTES = arrayOf(
"", "Sick", "Forgot to do it, really", "", "", "", "\"Vacation\"", "", "", ""
)
fun createEmptyHabit(
name: String = "Meditate",
color: PaletteColor = PaletteColor(3),
@@ -141,10 +145,10 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis
habit.frequency = Frequency(2, 3)
saveIfSQLite(habit)
var timestamp = getToday()
for (c in NON_DAILY_HABIT_CHECKS) {
for (i in NON_DAILY_HABIT_CHECKS.indices) {
var value = Entry.NO
if (c) value = Entry.YES_MANUAL
habit.originalEntries.add(Entry(timestamp, value))
if (NON_DAILY_HABIT_CHECKS[i]) value = Entry.YES_MANUAL
habit.originalEntries.add(Entry(timestamp, value, NON_DAILY_HABIT_NOTES[i]))
timestamp = timestamp.minus(1)
}
habit.recompute()

View File

@@ -18,7 +18,6 @@
*/
package org.isoron.uhabits.core.io
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
@@ -30,6 +29,7 @@ import java.io.IOException
import java.nio.file.Files
import java.util.*
import java.util.zip.ZipFile
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class HabitsCSVExporterTest : BaseUnitTest() {
@@ -108,15 +108,12 @@ class HabitsCSVExporterTest : BaseUnitTest() {
private fun assertFileAndReferenceAreEqual(s: String) {
val assetFilename = String.format("csv_export/%s", s)
val file = File.createTempFile("asset", "")
file.deleteOnExit()
copyAssetToFile(assetFilename, file)
assertTrue(
FileUtils.contentEquals(
file,
File(String.format("%s/%s", baseDir.absolutePath, s))
)
)
val actualFile = File(String.format("%s/%s", baseDir.absolutePath, s))
val expectedFile = File.createTempFile("asset", "")
expectedFile.deleteOnExit()
copyAssetToFile(assetFilename, expectedFile)
val actualContents = actualFile.readText()
val expectedContents = expectedFile.readText()
assertEquals(expectedContents, actualContents, "content mismatch for $s")
}
}

View File

@@ -202,13 +202,16 @@ class HabitListTest : BaseUnitTest() {
h2.description = ""
h2.frequency = Frequency(2, 3)
h2.color = PaletteColor(5)
val h3 = fixtures.createNumericalHabit()
list.add(h1)
list.add(h2)
list.add(h3)
val expectedCSV =
"""
Position,Name,Question,Description,NumRepetitions,Interval,Color
001,Meditate,Did you meditate this morning?,this is a test description,1,1,#FF8F00
002,Wake up early,Did you wake up before 6am?,,2,3,#AFB42B
Position,Name,Type,Question,Description,FrequencyNumerator,FrequencyDenominator,Color,Unit,Target Type,Target Value,Archived?
001,Meditate,YES_NO,Did you meditate this morning?,this is a test description,1,1,#FF8F00,,,,false
002,Run,NUMERICAL,How many miles did you run today?,,1,1,#E64A19,miles,AT_LEAST,2.0,false
003,Wake up early,YES_NO,Did you wake up before 6am?,,2,3,#AFB42B,,,,false
""".trimIndent()
val writer = StringWriter()