Replace BarChart by new Kotlin implementation

This commit is contained in:
2020-12-31 15:20:00 -06:00
parent e97cdce467
commit 354c930d85
38 changed files with 339 additions and 634 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -19,6 +19,11 @@
package org.isoron.platform.gui
interface Component {
interface View {
fun draw(canvas: Canvas)
}
interface DataView : View {
var dataOffset: Int
val dataColumnWidth: Double
}

View File

@@ -19,6 +19,7 @@
package org.isoron.platform.time
import org.isoron.uhabits.core.models.Timestamp
import kotlin.math.abs
import kotlin.math.ceil
@@ -32,15 +33,6 @@ enum class DayOfWeek(val index: Int) {
SATURDAY(6),
}
data class Timestamp(val millisSince1970: Long) {
val localDate: LocalDate
get() {
val millisSince2000 = millisSince1970 - 946684800000
val daysSince2000 = millisSince2000 / 86400000
return LocalDate(daysSince2000.toInt())
}
}
data class LocalDate(val daysSince2000: Int) {
var yearCache = -1

View File

@@ -20,9 +20,11 @@
package org.isoron.uhabits.core.models;
import org.apache.commons.lang3.builder.*;
import org.isoron.platform.time.LocalDate;
import org.isoron.uhabits.core.utils.*;
import org.jetbrains.annotations.*;
import java.time.*;
import java.util.*;
import kotlin.*;
@@ -66,6 +68,13 @@ public final class Timestamp implements Comparable<Timestamp>
return unixTime;
}
public LocalDate toLocalDate()
{
long millisSince2000 = unixTime - 946684800000L;
int daysSince2000 = (int) (millisSince2000 / 86400000);
return new LocalDate(daysSince2000);
}
/**
* Returns -1 if this timestamp is older than the given timestamp, 1 if this
* timestamp is newer, or zero if they are equal.

View File

@@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui;
import androidx.annotation.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.ui.views.*;
public abstract class ThemeSwitcher
{
@@ -59,6 +60,8 @@ public abstract class ThemeSwitcher
public abstract int getSystemTheme();
public abstract Theme getCurrentTheme();
public boolean isNightMode()
{
int systemTheme = getSystemTheme();

View File

@@ -40,6 +40,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardPresente
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardViewModel
import org.isoron.uhabits.core.ui.views.Theme
data class ShowHabitViewModel(
val title: String = "",
@@ -60,6 +61,7 @@ class ShowHabitPresenter {
fun present(
habit: Habit,
preferences: Preferences,
theme: Theme,
): ShowHabitViewModel {
return ShowHabitViewModel(
title = habit.name,
@@ -100,6 +102,7 @@ class ShowHabitPresenter {
firstWeekday = preferences.firstWeekday,
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
theme = theme,
),
)
}

View File

@@ -23,9 +23,11 @@ import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
data class BarCardViewModel(
val theme: Theme,
val boolSpinnerPosition: Int,
val bucketSize: Int,
val color: PaletteColor,
@@ -43,6 +45,7 @@ class BarCardPresenter {
firstWeekday: Int,
numericalSpinnerPosition: Int,
boolSpinnerPosition: Int,
theme: Theme,
): BarCardViewModel {
val bucketSize = if (habit.isNumerical) {
numericalBucketSizes[numericalSpinnerPosition]
@@ -57,6 +60,7 @@ class BarCardPresenter {
isNumerical = habit.isNumerical,
)
return BarCardViewModel(
theme = theme,
entries = entries,
bucketSize = bucketSize,
color = habit.color,

View File

@@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.DataView
import org.isoron.platform.gui.TextAlign
import org.isoron.platform.time.LocalDate
import org.isoron.platform.time.LocalDateFormatter
@@ -32,12 +32,13 @@ import kotlin.math.round
class BarChart(
var theme: Theme,
var dateFormatter: LocalDateFormatter,
) : Component {
) : DataView {
// Data
var series = mutableListOf<List<Double>>()
var colors = mutableListOf<Color>()
var axis = listOf<LocalDate>()
override var dataOffset = 0
// Style
var paddingTop = 20.0
@@ -50,6 +51,9 @@ class BarChart(
var nGridlines = 6
var backgroundColor = theme.cardBackgroundColor
override val dataColumnWidth: Double
get() = barWidth + barMargin * 2
override fun draw(canvas: Canvas) {
val width = canvas.getWidth()
val height = canvas.getHeight()
@@ -75,8 +79,11 @@ class BarChart(
barMargin
fun drawColumn(s: Int, c: Int) {
val dataColumn = nColumns - c - 1
val value = if (dataColumn < series[s].size) series[s][dataColumn] else 0.0
val dataColumn = nColumns - c - 1 + dataOffset
val value = when {
dataColumn < 0 || dataColumn >= series[s].size -> 0.0
else -> series[s][dataColumn]
}
if (value <= 0) return
val perc = value / maxValue
val barHeight = round(maxBarHeight * perc)
@@ -142,12 +149,12 @@ class BarChart(
canvas.setTextAlign(TextAlign.CENTER)
var prevMonth = -1
var prevYear = -1
val isLargeInterval = (axis[0].distanceTo(axis[1]) > 300)
val isLargeInterval = axis.size < 2 || (axis[0].distanceTo(axis[1]) > 300)
for (c in 0 until nColumns) {
val x = barGroupOffset(c)
val dataColumn = nColumns - c - 1
if (dataColumn >= axis.size) continue
val dataColumn = nColumns - c - 1 + dataOffset
if (dataColumn < 0 || dataColumn >= axis.size) continue
val date = axis[dataColumn]
if (isLargeInterval) {
canvas.drawText(

View File

@@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.TextAlign
import org.isoron.platform.gui.View
import org.isoron.platform.time.LocalDate
import org.isoron.platform.time.LocalDateFormatter
import kotlin.math.floor
@@ -33,7 +33,7 @@ class CalendarChart(
var color: Color,
var theme: Theme,
var dateFormatter: LocalDateFormatter
) : Component {
) : View {
var padding = 5.0
var backgroundColor = Color(0xFFFFFF)

View File

@@ -21,15 +21,15 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.Font
import org.isoron.platform.gui.FontAwesome
import org.isoron.platform.gui.View
class CheckmarkButton(
private val value: Int,
private val color: Color,
private val theme: Theme
) : Component {
) : View {
override fun draw(canvas: Canvas) {
canvas.setFont(Font.FONT_AWESOME)
canvas.setFontSize(theme.smallTextSize * 1.5)

View File

@@ -20,8 +20,8 @@
package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.Font
import org.isoron.platform.gui.View
import org.isoron.platform.time.LocalDate
import org.isoron.platform.time.LocalDateFormatter
@@ -30,7 +30,7 @@ class HabitListHeader(
private val nButtons: Int,
private val theme: Theme,
private val fmt: LocalDateFormatter
) : Component {
) : View {
override fun draw(canvas: Canvas) {
val width = canvas.getWidth()

View File

@@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.Font
import org.isoron.platform.gui.View
import java.lang.String.format
import kotlin.math.round
@@ -52,7 +52,7 @@ class NumberButton(
val threshold: Double,
val units: String,
val theme: Theme
) : Component {
) : View {
override fun draw(canvas: Canvas) {
val width = canvas.getWidth()

View File

@@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color
import org.isoron.platform.gui.Component
import org.isoron.platform.gui.View
import java.lang.String.format
import kotlin.math.max
import kotlin.math.min
@@ -33,7 +33,7 @@ class Ring(
val radius: Double,
val theme: Theme,
val label: Boolean = false
) : Component {
) : View {
override fun draw(canvas: Canvas) {
val width = canvas.getWidth()

View File

@@ -22,27 +22,23 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Color
abstract class Theme {
val toolbarColor = Color(0xffffff)
open val appBackgroundColor = Color(0xf4f4f4)
open val cardBackgroundColor = Color(0xFAFAFA)
open val headerBackgroundColor = Color(0xeeeeee)
open val headerBorderColor = Color(0xcccccc)
open val headerTextColor = Color(0x9E9E9E)
open val highContrastTextColor = Color(0x202020)
open val itemBackgroundColor = Color(0xffffff)
open val lowContrastTextColor = Color(0xe0e0e0)
open val mediumContrastTextColor = Color(0x9E9E9E)
open val statusBarBackgroundColor = Color(0x333333)
open val toolbarBackgroundColor = Color(0xf4f4f4)
open val toolbarColor = Color(0xffffff)
val lowContrastTextColor = Color(0xe0e0e0)
val mediumContrastTextColor = Color(0x9E9E9E)
val highContrastTextColor = Color(0x202020)
val cardBackgroundColor = Color(0xFFFFFF)
val appBackgroundColor = Color(0xf4f4f4)
val toolbarBackgroundColor = Color(0xf4f4f4)
val statusBarBackgroundColor = Color(0x333333)
val headerBackgroundColor = Color(0xeeeeee)
val headerBorderColor = Color(0xcccccc)
val headerTextColor = mediumContrastTextColor
val itemBackgroundColor = Color(0xffffff)
fun color(paletteIndex: Int): Color {
open fun color(paletteIndex: Int): Color {
return when (paletteIndex) {
0 -> Color(0xD32F2F)
1 -> Color(0x512DA8)
1 -> Color(0xE64A19)
2 -> Color(0xF57C00)
3 -> Color(0xFF8F00)
4 -> Color(0xF9A825)
@@ -58,6 +54,9 @@ abstract class Theme {
14 -> Color(0x8E24AA)
15 -> Color(0xD81B60)
16 -> Color(0x5D4037)
17 -> Color(0x424242)
18 -> Color(0x757575)
19 -> Color(0x9E9E9E)
else -> Color(0x000000)
}
}
@@ -68,3 +67,44 @@ abstract class Theme {
}
class LightTheme : Theme()
class DarkTheme : Theme() {
override val appBackgroundColor = Color(0x212121)
override val cardBackgroundColor = Color(0x303030)
override val headerBackgroundColor = Color(0x212121)
override val headerBorderColor = Color(0xcccccc)
override val headerTextColor = Color(0x9E9E9E)
override val highContrastTextColor = Color(0xF5F5F5)
override val itemBackgroundColor = Color(0xffffff)
override val lowContrastTextColor = Color(0x424242)
override val mediumContrastTextColor = Color(0x9E9E9E)
override val statusBarBackgroundColor = Color(0x333333)
override val toolbarBackgroundColor = Color(0xf4f4f4)
override val toolbarColor = Color(0xffffff)
override fun color(paletteIndex: Int): Color {
return when (paletteIndex) {
0 -> Color(0xEF9A9A)
1 -> Color(0xFFAB91)
2 -> Color(0xFFCC80)
3 -> Color(0xFFECB3)
4 -> Color(0xFFF59D)
5 -> Color(0xE6EE9C)
6 -> Color(0xC5E1A5)
7 -> Color(0x69F0AE)
8 -> Color(0x80CBC4)
9 -> Color(0x80DEEA)
10 -> Color(0x81D4FA)
11 -> Color(0x64B5F6)
12 -> Color(0x9FA8DA)
13 -> Color(0xB39DDB)
14 -> Color(0xCE93D8)
15 -> Color(0xF48FB1)
16 -> Color(0xBCAAA4)
17 -> Color(0xF5F5F5)
18 -> Color(0xE0E0E0)
19 -> Color(0x9E9E9E)
else -> Color(0xFFFFFF)
}
}
}

View File

@@ -29,7 +29,7 @@ import java.awt.image.BufferedImage.TYPE_INT_ARGB
class JavaCanvasTest {
@Test
fun run() = runBlocking {
assertRenders("components/CanvasTest.png", createCanvas(500, 400).apply { drawTestImage() })
assertRenders("views/CanvasTest.png", createCanvas(500, 400).apply { drawTestImage() })
}
}
@@ -69,9 +69,9 @@ suspend fun assertRenders(
width: Int,
height: Int,
expectedPath: String,
component: Component,
view: View,
) {
val canvas = createCanvas(width, height)
component.draw(canvas)
view.draw(canvas)
assertRenders(expectedPath, canvas)
}

View File

@@ -17,19 +17,17 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.components
package org.isoron.uhabits.core.ui.views
import kotlinx.coroutines.runBlocking
import org.isoron.platform.gui.assertRenders
import org.isoron.platform.time.JavaLocalDateFormatter
import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.ui.views.BarChart
import org.isoron.uhabits.core.ui.views.LightTheme
import org.junit.Test
import java.util.Locale
class BarChartTest {
val base = "components/BarChart"
val base = "views/BarChart"
val today = LocalDate(2015, 1, 25)
val fmt = JavaLocalDateFormatter(Locale.US)
val theme = LightTheme()
@@ -37,11 +35,20 @@ class BarChartTest {
val axis = (0..100).map { today.minus(it) }
val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0)
@Test
fun testDraw() = runBlocking {
init {
component.axis = axis
component.series.add(series1)
component.colors.add(theme.color(8))
}
@Test
fun testDraw() = runBlocking {
assertRenders(300, 200, "$base/base.png", component)
}
@Test
fun testDrawWithOffset() = runBlocking {
component.dataOffset = 5
assertRenders(300, 200, "$base/offset.png", component)
}
}