HistoryChart: Fix transparency
@@ -26,8 +26,8 @@ import org.isoron.platform.gui.AndroidDataView
|
|||||||
import org.isoron.platform.time.JavaLocalDateFormatter
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.views.DarkTheme
|
|
||||||
import org.isoron.uhabits.core.ui.views.HistoryChart
|
import org.isoron.uhabits.core.ui.views.HistoryChart
|
||||||
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -51,7 +51,7 @@ class HistoryWidget(
|
|||||||
habit = habit,
|
habit = habit,
|
||||||
isSkipEnabled = prefs.isSkipEnabled,
|
isSkipEnabled = prefs.isSkipEnabled,
|
||||||
firstWeekday = prefs.firstWeekday,
|
firstWeekday = prefs.firstWeekday,
|
||||||
theme = DarkTheme(),
|
theme = WidgetTheme(),
|
||||||
)
|
)
|
||||||
(widgetView.dataView as AndroidDataView).apply {
|
(widgetView.dataView as AndroidDataView).apply {
|
||||||
(this.view as HistoryChart).series = model.series
|
(this.view as HistoryChart).series = model.series
|
||||||
@@ -65,7 +65,7 @@ class HistoryWidget(
|
|||||||
view = HistoryChart(
|
view = HistoryChart(
|
||||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
paletteColor = habit.color,
|
paletteColor = habit.color,
|
||||||
theme = DarkTheme(),
|
theme = WidgetTheme(),
|
||||||
dateFormatter = JavaLocalDateFormatter(Locale.getDefault())
|
dateFormatter = JavaLocalDateFormatter(Locale.getDefault())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="vertical" android:layout_width="match_parent"
|
android:orientation="vertical" android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:background="#ffff00">
|
||||||
|
|
||||||
<org.isoron.platform.gui.AndroidTestView
|
<org.isoron.platform.gui.AndroidTestView
|
||||||
android:layout_width="500dp"
|
android:layout_width="500dp"
|
||||||
|
|||||||
BIN
android/uhabits-core/assets/test/views/BarChart/themeDark.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
android/uhabits-core/assets/test/views/BarChart/themeWidget.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -52,9 +52,16 @@ interface Canvas {
|
|||||||
fun setTextAlign(align: TextAlign)
|
fun setTextAlign(align: TextAlign)
|
||||||
fun toImage(): Image
|
fun toImage(): Image
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills entire canvas with the current color.
|
||||||
|
*/
|
||||||
|
fun fill() {
|
||||||
|
fillRect(0.0, 0.0, getWidth(), getHeight())
|
||||||
|
}
|
||||||
|
|
||||||
fun drawTestImage() {
|
fun drawTestImage() {
|
||||||
// Draw grey background
|
// Draw transparent background
|
||||||
setColor(Color(0x303030))
|
setColor(Color(0.1, 0.1, 0.1, 0.5))
|
||||||
fillRect(0.0, 0.0, 500.0, 400.0)
|
fillRect(0.0, 0.0, 500.0, 400.0)
|
||||||
|
|
||||||
// Draw center rectangle
|
// Draw center rectangle
|
||||||
@@ -63,7 +70,7 @@ interface Canvas {
|
|||||||
drawRect(100.0, 100.0, 300.0, 200.0)
|
drawRect(100.0, 100.0, 300.0, 200.0)
|
||||||
|
|
||||||
// Draw squares, circles and arcs
|
// Draw squares, circles and arcs
|
||||||
setColor(Color(0xFFFF00))
|
setColor(Color.YELLOW)
|
||||||
setStrokeWidth(1.0)
|
setStrokeWidth(1.0)
|
||||||
drawRect(0.0, 0.0, 100.0, 100.0)
|
drawRect(0.0, 0.0, 100.0, 100.0)
|
||||||
fillCircle(50.0, 50.0, 30.0)
|
fillCircle(50.0, 50.0, 30.0)
|
||||||
@@ -75,7 +82,7 @@ interface Canvas {
|
|||||||
fillArc(50.0, 350.0, 30.0, 45.0, 90.0)
|
fillArc(50.0, 350.0, 30.0, 45.0, 90.0)
|
||||||
|
|
||||||
// Draw two red crossing lines
|
// Draw two red crossing lines
|
||||||
setColor(Color(0xFF0000))
|
setColor(Color.RED)
|
||||||
setStrokeWidth(2.0)
|
setStrokeWidth(2.0)
|
||||||
drawLine(0.0, 0.0, 500.0, 400.0)
|
drawLine(0.0, 0.0, 500.0, 400.0)
|
||||||
drawLine(500.0, 0.0, 0.0, 400.0)
|
drawLine(500.0, 0.0, 0.0, 400.0)
|
||||||
@@ -83,7 +90,7 @@ interface Canvas {
|
|||||||
// Draw text
|
// Draw text
|
||||||
setFont(Font.BOLD)
|
setFont(Font.BOLD)
|
||||||
setFontSize(50.0)
|
setFontSize(50.0)
|
||||||
setColor(Color(0x00FF00))
|
setColor(Color.GREEN)
|
||||||
setTextAlign(TextAlign.CENTER)
|
setTextAlign(TextAlign.CENTER)
|
||||||
drawText("HELLO", 250.0, 100.0)
|
drawText("HELLO", 250.0, 100.0)
|
||||||
setTextAlign(TextAlign.RIGHT)
|
setTextAlign(TextAlign.RIGHT)
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ data class Color(
|
|||||||
val red: Double,
|
val red: Double,
|
||||||
val green: Double,
|
val green: Double,
|
||||||
val blue: Double,
|
val blue: Double,
|
||||||
val alpha: Double
|
val alpha: Double,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val luminosity: Double
|
val luminosity: Double
|
||||||
get() {
|
get() {
|
||||||
return 0.21 * red + 0.72 * green + 0.07 * blue
|
return 0.21 * red + 0.72 * green + 0.07 * blue
|
||||||
@@ -53,4 +52,18 @@ data class Color(
|
|||||||
val relativeLuminosity = (l1 + 0.05) / (l2 + 0.05)
|
val relativeLuminosity = (l1 + 0.05) / (l2 + 0.05)
|
||||||
return if (relativeLuminosity >= 1) relativeLuminosity else 1 / relativeLuminosity
|
return if (relativeLuminosity >= 1) relativeLuminosity else 1 / relativeLuminosity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withAlpha(newAlpha: Double) = Color(red, green, blue, newAlpha)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRANSPARENT = Color(0.0, 0.0, 0.0, 0.0)
|
||||||
|
val RED = Color(1.0, 0.0, 0.0, 1.0)
|
||||||
|
val GREEN = Color(0.0, 1.0, 0.0, 1.0)
|
||||||
|
val BLUE = Color(1.0, 0.0, 1.0, 1.0)
|
||||||
|
val YELLOW = Color(1.0, 1.0, 0.0, 1.0)
|
||||||
|
val MAGENTA = Color(1.0, 0.0, 1.0, 1.0)
|
||||||
|
val CYAN = Color(0.0, 1.0, 1.0, 1.0)
|
||||||
|
val WHITE = Color(1.0, 1.0, 1.0, 1.0)
|
||||||
|
val BLACK = Color(0.0, 0.0, 0.0, 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class BarChart(
|
|||||||
var barMargin = 3.0
|
var barMargin = 3.0
|
||||||
var barWidth = 12.0
|
var barWidth = 12.0
|
||||||
var nGridlines = 6
|
var nGridlines = 6
|
||||||
var backgroundColor = theme.cardBackgroundColor
|
|
||||||
|
|
||||||
override val dataColumnWidth: Double
|
override val dataColumnWidth: Double
|
||||||
get() = barWidth + barMargin * 2
|
get() = barWidth + barMargin * 2
|
||||||
@@ -67,8 +66,8 @@ class BarChart(
|
|||||||
var maxValue = series.map { it.max()!! }.max()!!
|
var maxValue = series.map { it.max()!! }.max()!!
|
||||||
maxValue = max(maxValue, 1.0)
|
maxValue = max(maxValue, 1.0)
|
||||||
|
|
||||||
canvas.setColor(backgroundColor)
|
canvas.setColor(theme.cardBackgroundColor)
|
||||||
canvas.fillRect(0.0, 0.0, width, height)
|
canvas.fill()
|
||||||
|
|
||||||
fun barGroupOffset(c: Int) = marginLeft + paddingLeft +
|
fun barGroupOffset(c: Int) = marginLeft + paddingLeft +
|
||||||
(c) * barGroupWidth
|
(c) * barGroupWidth
|
||||||
@@ -97,13 +96,6 @@ class BarChart(
|
|||||||
canvas.fillCircle(x + barWidth - r, y + r, r)
|
canvas.fillCircle(x + barWidth - r, y + r, r)
|
||||||
canvas.setFontSize(theme.smallTextSize)
|
canvas.setFontSize(theme.smallTextSize)
|
||||||
canvas.setTextAlign(TextAlign.CENTER)
|
canvas.setTextAlign(TextAlign.CENTER)
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(
|
|
||||||
x - barMargin,
|
|
||||||
y - theme.smallTextSize * 1.25,
|
|
||||||
barWidth + 2 * barMargin,
|
|
||||||
theme.smallTextSize * 1.0
|
|
||||||
)
|
|
||||||
canvas.setColor(colors[s])
|
canvas.setColor(colors[s])
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
value.toShortString(),
|
value.toShortString(),
|
||||||
@@ -119,12 +111,7 @@ class BarChart(
|
|||||||
fun drawMajorGrid() {
|
fun drawMajorGrid() {
|
||||||
canvas.setStrokeWidth(1.0)
|
canvas.setStrokeWidth(1.0)
|
||||||
if (nSeries > 1) {
|
if (nSeries > 1) {
|
||||||
canvas.setColor(
|
canvas.setColor(theme.lowContrastTextColor.withAlpha(0.5))
|
||||||
backgroundColor.blendWith(
|
|
||||||
theme.lowContrastTextColor,
|
|
||||||
0.5
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for (c in 0 until nColumns - 1) {
|
for (c in 0 until nColumns - 1) {
|
||||||
val x = barGroupOffset(c)
|
val x = barGroupOffset(c)
|
||||||
canvas.drawLine(x, paddingTop, x, paddingTop + maxBarHeight)
|
canvas.drawLine(x, paddingTop, x, paddingTop + maxBarHeight)
|
||||||
@@ -141,8 +128,6 @@ class BarChart(
|
|||||||
|
|
||||||
fun drawAxis() {
|
fun drawAxis() {
|
||||||
val y = paddingTop + maxBarHeight
|
val y = paddingTop + maxBarHeight
|
||||||
canvas.setColor(backgroundColor)
|
|
||||||
canvas.fillRect(0.0, y, width, height - y)
|
|
||||||
canvas.setColor(theme.lowContrastTextColor)
|
canvas.setColor(theme.lowContrastTextColor)
|
||||||
canvas.drawLine(0.0, y, width, y)
|
canvas.drawLine(0.0, y, width, y)
|
||||||
canvas.setColor(theme.mediumContrastTextColor)
|
canvas.setColor(theme.mediumContrastTextColor)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class HistoryChart(
|
|||||||
var today: LocalDate,
|
var today: LocalDate,
|
||||||
var paletteColor: PaletteColor,
|
var paletteColor: PaletteColor,
|
||||||
var theme: Theme,
|
var theme: Theme,
|
||||||
var dateFormatter: LocalDateFormatter
|
var dateFormatter: LocalDateFormatter,
|
||||||
) : DataView {
|
) : DataView {
|
||||||
|
|
||||||
enum class Square {
|
enum class Square {
|
||||||
@@ -61,8 +61,10 @@ class HistoryChart(
|
|||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
val width = canvas.getWidth()
|
||||||
val height = canvas.getHeight()
|
val height = canvas.getHeight()
|
||||||
|
|
||||||
canvas.setColor(theme.cardBackgroundColor)
|
canvas.setColor(theme.cardBackgroundColor)
|
||||||
canvas.fillRect(0.0, 0.0, width, height)
|
canvas.fill()
|
||||||
|
|
||||||
squareSize = round((height - 2 * padding) / 8.0)
|
squareSize = round((height - 2 * padding) / 8.0)
|
||||||
canvas.setFontSize(height * 0.06)
|
canvas.setFontSize(height * 0.06)
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ class HistoryChart(
|
|||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
column: Int,
|
column: Int,
|
||||||
topDate: LocalDate,
|
topDate: LocalDate,
|
||||||
topOffset: Int
|
topOffset: Int,
|
||||||
) {
|
) {
|
||||||
drawHeader(canvas, column, topDate)
|
drawHeader(canvas, column, topDate)
|
||||||
repeat(7) { row ->
|
repeat(7) { row ->
|
||||||
@@ -150,7 +152,7 @@ class HistoryChart(
|
|||||||
width: Double,
|
width: Double,
|
||||||
height: Double,
|
height: Double,
|
||||||
date: LocalDate,
|
date: LocalDate,
|
||||||
offset: Int
|
offset: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val value = if (offset >= series.size) Square.OFF else series[offset]
|
val value = if (offset >= series.size) Square.OFF else series[offset]
|
||||||
@@ -187,9 +189,13 @@ class HistoryChart(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val c1 = squareColor.contrast(theme.cardBackgroundColor)
|
val textColor = if (theme.cardBackgroundColor == Color.TRANSPARENT) {
|
||||||
val c2 = squareColor.contrast(theme.mediumContrastTextColor)
|
theme.highContrastTextColor
|
||||||
val textColor = if (c1 > c2) theme.cardBackgroundColor else theme.mediumContrastTextColor
|
} else {
|
||||||
|
val c1 = squareColor.contrast(theme.cardBackgroundColor)
|
||||||
|
val c2 = squareColor.contrast(theme.mediumContrastTextColor)
|
||||||
|
if (c1 > c2) theme.cardBackgroundColor else theme.mediumContrastTextColor
|
||||||
|
}
|
||||||
|
|
||||||
canvas.setColor(textColor)
|
canvas.setColor(textColor)
|
||||||
canvas.setTextAlign(TextAlign.CENTER)
|
canvas.setTextAlign(TextAlign.CENTER)
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ abstract class Theme {
|
|||||||
val regularTextSize = 17.0
|
val regularTextSize = 17.0
|
||||||
}
|
}
|
||||||
|
|
||||||
class LightTheme : Theme()
|
open class LightTheme : Theme()
|
||||||
|
|
||||||
class DarkTheme : Theme() {
|
open class DarkTheme : Theme() {
|
||||||
override val appBackgroundColor = Color(0x212121)
|
override val appBackgroundColor = Color(0x212121)
|
||||||
override val cardBackgroundColor = Color(0x303030)
|
override val cardBackgroundColor = Color(0x303030)
|
||||||
override val headerBackgroundColor = Color(0x212121)
|
override val headerBackgroundColor = Color(0x212121)
|
||||||
@@ -108,3 +108,10 @@ class DarkTheme : Theme() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WidgetTheme : LightTheme() {
|
||||||
|
override val cardBackgroundColor = Color.TRANSPARENT
|
||||||
|
override val highContrastTextColor = Color.WHITE
|
||||||
|
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
||||||
|
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
|
||||||
|
}
|
||||||
@@ -46,6 +46,18 @@ class BarChartTest {
|
|||||||
assertRenders(300, 200, "$base/base.png", component)
|
assertRenders(300, 200, "$base/base.png", component)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawDarkTheme() = runBlocking {
|
||||||
|
component.theme = DarkTheme()
|
||||||
|
assertRenders(300, 200, "$base/themeDark.png", component)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawWidgetTheme() = runBlocking {
|
||||||
|
component.theme = WidgetTheme()
|
||||||
|
assertRenders(300, 200, "$base/themeWidget.png", component)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDrawWithOffset() = runBlocking {
|
fun testDrawWithOffset() = runBlocking {
|
||||||
component.dataOffset = 5
|
component.dataOffset = 5
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED
|
|||||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
|
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
|
||||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
|
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
|
||||||
import org.isoron.uhabits.core.ui.views.LightTheme
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -70,7 +71,6 @@ class HistoryChartTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Label overflow
|
// TODO: Label overflow
|
||||||
// TODO: Transparent
|
|
||||||
// TODO: onClick
|
// TODO: onClick
|
||||||
// TODO: HistoryEditorDialog
|
// TODO: HistoryEditorDialog
|
||||||
// TODO: Remove excessive padding on widgets
|
// TODO: Remove excessive padding on widgets
|
||||||
@@ -89,7 +89,13 @@ class HistoryChartTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testDrawDarkTheme() = runBlocking {
|
fun testDrawDarkTheme() = runBlocking {
|
||||||
view.theme = DarkTheme()
|
view.theme = DarkTheme()
|
||||||
assertRenders(400, 200, "$base/dark.png", view)
|
assertRenders(400, 200, "$base/themeDark.png", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawWidgetTheme() = runBlocking {
|
||||||
|
view.theme = WidgetTheme()
|
||||||
|
assertRenders(400, 200, "$base/themeWidget.png", view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||