Replace BarChart by new Kotlin implementation
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.activities.common.views;
|
|
||||||
|
|
||||||
import androidx.test.filters.*;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
import org.junit.*;
|
|
||||||
import org.junit.runner.*;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@MediumTest
|
|
||||||
public class BarChartTest extends BaseViewTest
|
|
||||||
{
|
|
||||||
private static final String BASE_PATH = "common/BarChart/";
|
|
||||||
|
|
||||||
private BarChart view;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
Habit habit = fixtures.createLongNumericalHabit();
|
|
||||||
view = new BarChart(targetContext);
|
|
||||||
Timestamp today = DateUtils.getToday();
|
|
||||||
EntryList entries = habit.getComputedEntries();
|
|
||||||
view.setEntries(entries.getByInterval(today.minus(20), today));
|
|
||||||
view.setColor(PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), targetContext));
|
|
||||||
measureView(view, dpToPixels(300), dpToPixels(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender() throws Throwable
|
|
||||||
{
|
|
||||||
assertRenders(view, BASE_PATH + "render.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDataOffset() throws Throwable
|
|
||||||
{
|
|
||||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
|
||||||
view.invalidate();
|
|
||||||
|
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDifferentSize() throws Throwable
|
|
||||||
{
|
|
||||||
measureView(view, dpToPixels(200), dpToPixels(200));
|
|
||||||
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsTransparencyEnabled(true);
|
|
||||||
assertRenders(view, BASE_PATH + "renderTransparent.png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,8 +25,6 @@ import android.graphics.Paint
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
|
||||||
|
|
||||||
class AndroidCanvas : Canvas {
|
class AndroidCanvas : Canvas {
|
||||||
@@ -107,7 +105,7 @@ class AndroidCanvas : Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setFontSize(size: Double) {
|
override fun setFontSize(size: Double) {
|
||||||
textPaint.textSize = size.toDp() * 1.07f
|
textPaint.textSize = size.toDp()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setStrokeWidth(size: Double) {
|
override fun setStrokeWidth(size: Double) {
|
||||||
@@ -156,14 +154,3 @@ class AndroidCanvas : Canvas {
|
|||||||
return AndroidImage(bmp)
|
return AndroidImage(bmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidCanvasTestView(context: Context, attrs: AttributeSet) : View(context, attrs) {
|
|
||||||
val canvas = AndroidCanvas()
|
|
||||||
|
|
||||||
override fun onDraw(canvas: android.graphics.Canvas) {
|
|
||||||
this.canvas.context = context
|
|
||||||
this.canvas.innerCanvas = canvas
|
|
||||||
this.canvas.density = resources.displayMetrics.density.toDouble()
|
|
||||||
this.canvas.drawTestImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.Scroller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AndroidView that implements scrolling.
|
||||||
|
*/
|
||||||
|
class AndroidDataView(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet,
|
||||||
|
) : AndroidView<DataView>(context, attrs),
|
||||||
|
GestureDetector.OnGestureListener,
|
||||||
|
ValueAnimator.AnimatorUpdateListener {
|
||||||
|
|
||||||
|
private val detector = GestureDetector(context, this)
|
||||||
|
private val scroller = Scroller(context, null, true)
|
||||||
|
private val scrollAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||||
|
addUpdateListener(this@AndroidDataView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
||||||
|
override fun onDown(e: MotionEvent?) = true
|
||||||
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
|
override fun onSingleTapUp(e: MotionEvent?) = false
|
||||||
|
override fun onLongPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
|
override fun onScroll(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
dx: Float,
|
||||||
|
dy: Float,
|
||||||
|
): Boolean {
|
||||||
|
if (Math.abs(dx) > Math.abs(dy)) {
|
||||||
|
val parent = parent
|
||||||
|
parent?.requestDisallowInterceptTouchEvent(true)
|
||||||
|
}
|
||||||
|
scroller.startScroll(
|
||||||
|
scroller.currX,
|
||||||
|
scroller.currY,
|
||||||
|
-dx.toInt(),
|
||||||
|
dy.toInt(),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float,
|
||||||
|
): Boolean {
|
||||||
|
scroller.fling(scroller.currX, scroller.currY, velocityX.toInt() / 2, 0, 0, 10000, 0, 0)
|
||||||
|
invalidate()
|
||||||
|
scrollAnimator.duration = scroller.duration.toLong()
|
||||||
|
scrollAnimator.start()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||||
|
if (!scroller.isFinished) {
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
} else {
|
||||||
|
scrollAnimator.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetDataOffset() {
|
||||||
|
scroller.finalX = 0
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDataOffset() {
|
||||||
|
var newDataOffset: Int = scroller.currX / (view.dataColumnWidth * canvas.density).toInt()
|
||||||
|
newDataOffset = Math.max(0, newDataOffset)
|
||||||
|
if (newDataOffset != view.dataOffset) {
|
||||||
|
view.dataOffset = newDataOffset
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
class AndroidTestView(context: Context, attrs: AttributeSet) : android.view.View(context, attrs) {
|
||||||
|
val canvas = AndroidCanvas()
|
||||||
|
|
||||||
|
override fun onDraw(canvas: android.graphics.Canvas) {
|
||||||
|
this.canvas.context = context
|
||||||
|
this.canvas.innerCanvas = canvas
|
||||||
|
this.canvas.density = resources.displayMetrics.density.toDouble()
|
||||||
|
this.canvas.drawTestImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
open class AndroidView<T : View>(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet,
|
||||||
|
) : android.view.View(context, attrs) {
|
||||||
|
|
||||||
|
lateinit var view: T
|
||||||
|
val canvas = AndroidCanvas()
|
||||||
|
|
||||||
|
override fun onDraw(canvas: android.graphics.Canvas) {
|
||||||
|
this.canvas.context = context
|
||||||
|
this.canvas.innerCanvas = canvas
|
||||||
|
this.canvas.density = resources.displayMetrics.density.toDouble()
|
||||||
|
view.draw(this.canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,9 @@ import androidx.core.content.ContextCompat
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.ui.ThemeSwitcher
|
import org.isoron.uhabits.core.ui.ThemeSwitcher
|
||||||
|
import org.isoron.uhabits.core.ui.views.DarkTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import org.isoron.uhabits.inject.ActivityScope
|
import org.isoron.uhabits.inject.ActivityScope
|
||||||
|
|
||||||
@@ -35,9 +38,11 @@ import org.isoron.uhabits.inject.ActivityScope
|
|||||||
class AndroidThemeSwitcher
|
class AndroidThemeSwitcher
|
||||||
constructor(
|
constructor(
|
||||||
@ActivityContext val context: Context,
|
@ActivityContext val context: Context,
|
||||||
preferences: Preferences
|
preferences: Preferences,
|
||||||
) : ThemeSwitcher(preferences) {
|
) : ThemeSwitcher(preferences) {
|
||||||
|
|
||||||
|
private var currentTheme: Theme = LightTheme()
|
||||||
|
|
||||||
override fun getSystemTheme(): Int {
|
override fun getSystemTheme(): Int {
|
||||||
if (SDK_INT < 29) return THEME_LIGHT
|
if (SDK_INT < 29) return THEME_LIGHT
|
||||||
val uiMode = context.resources.configuration.uiMode
|
val uiMode = context.resources.configuration.uiMode
|
||||||
@@ -48,17 +53,24 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCurrentTheme(): Theme {
|
||||||
|
return currentTheme
|
||||||
|
}
|
||||||
|
|
||||||
override fun applyDarkTheme() {
|
override fun applyDarkTheme() {
|
||||||
|
currentTheme = DarkTheme()
|
||||||
context.setTheme(R.style.AppBaseThemeDark)
|
context.setTheme(R.style.AppBaseThemeDark)
|
||||||
(context as Activity).window.navigationBarColor =
|
(context as Activity).window.navigationBarColor =
|
||||||
ContextCompat.getColor(context, R.color.grey_900)
|
ContextCompat.getColor(context, R.color.grey_900)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyLightTheme() {
|
override fun applyLightTheme() {
|
||||||
|
currentTheme = LightTheme()
|
||||||
context.setTheme(R.style.AppBaseTheme)
|
context.setTheme(R.style.AppBaseTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyPureBlackTheme() {
|
override fun applyPureBlackTheme() {
|
||||||
|
currentTheme = DarkTheme()
|
||||||
context.setTheme(R.style.AppBaseThemeDark_PureBlack)
|
context.setTheme(R.style.AppBaseThemeDark_PureBlack)
|
||||||
(context as Activity).window.navigationBarColor =
|
(context as Activity).window.navigationBarColor =
|
||||||
ContextCompat.getColor(context, R.color.black)
|
ContextCompat.getColor(context, R.color.black)
|
||||||
|
|||||||
@@ -1,474 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.activities.common.views;
|
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.util.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
|
|
||||||
import java.text.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.*;
|
|
||||||
|
|
||||||
public class BarChart extends ScrollableChart
|
|
||||||
{
|
|
||||||
private static final PorterDuffXfermode XFERMODE_CLEAR =
|
|
||||||
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
|
||||||
|
|
||||||
private static final PorterDuffXfermode XFERMODE_SRC =
|
|
||||||
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
|
||||||
|
|
||||||
private Paint pGrid;
|
|
||||||
|
|
||||||
private float em;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfMonth;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfDay;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfYear;
|
|
||||||
|
|
||||||
private Paint pText, pGraph;
|
|
||||||
|
|
||||||
private RectF rect, prevRect;
|
|
||||||
|
|
||||||
private int baseSize;
|
|
||||||
|
|
||||||
private int paddingTop;
|
|
||||||
|
|
||||||
private float columnWidth;
|
|
||||||
|
|
||||||
private int columnHeight;
|
|
||||||
|
|
||||||
private int nColumns;
|
|
||||||
|
|
||||||
private int textColor;
|
|
||||||
|
|
||||||
private int gridColor;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<Entry> entries;
|
|
||||||
|
|
||||||
private int bucketSize = 7;
|
|
||||||
|
|
||||||
private int backgroundColor;
|
|
||||||
|
|
||||||
private Bitmap drawingCache;
|
|
||||||
|
|
||||||
private Canvas cacheCanvas;
|
|
||||||
|
|
||||||
private boolean isTransparencyEnabled;
|
|
||||||
|
|
||||||
private int skipYear = 0;
|
|
||||||
|
|
||||||
private String previousYearText;
|
|
||||||
|
|
||||||
private String previousMonthText;
|
|
||||||
|
|
||||||
private double maxValue;
|
|
||||||
|
|
||||||
private int primaryColor;
|
|
||||||
|
|
||||||
public BarChart(Context context)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BarChart(Context context, AttributeSet attrs)
|
|
||||||
{
|
|
||||||
super(context, attrs);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void populateWithRandomData()
|
|
||||||
{
|
|
||||||
Random random = new Random();
|
|
||||||
List<Entry> entries = new LinkedList<>();
|
|
||||||
|
|
||||||
Timestamp today = DateUtils.getToday();
|
|
||||||
|
|
||||||
for (int i = 1; i < 100; i++)
|
|
||||||
{
|
|
||||||
int value = random.nextInt(1000);
|
|
||||||
entries.add(new Entry(today.minus(i), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
setEntries(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBucketSize(int bucketSize)
|
|
||||||
{
|
|
||||||
this.bucketSize = bucketSize;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntries(@NonNull List<Entry> entries)
|
|
||||||
{
|
|
||||||
this.entries = entries;
|
|
||||||
|
|
||||||
maxValue = 1.0;
|
|
||||||
for (Entry e : entries)
|
|
||||||
maxValue = Math.max(maxValue, e.getValue());
|
|
||||||
maxValue = Math.ceil(maxValue / 1000 * 1.05) * 1000;
|
|
||||||
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColor(int primaryColor)
|
|
||||||
{
|
|
||||||
StyledResources res = new StyledResources(getContext());
|
|
||||||
this.primaryColor = primaryColor;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsTransparencyEnabled(boolean enabled)
|
|
||||||
{
|
|
||||||
this.isTransparencyEnabled = enabled;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas)
|
|
||||||
{
|
|
||||||
super.onDraw(canvas);
|
|
||||||
Canvas activeCanvas;
|
|
||||||
|
|
||||||
if (isTransparencyEnabled)
|
|
||||||
{
|
|
||||||
if (drawingCache == null) initCache(getWidth(), getHeight());
|
|
||||||
|
|
||||||
activeCanvas = cacheCanvas;
|
|
||||||
drawingCache.eraseColor(Color.TRANSPARENT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
activeCanvas = canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries == null) return;
|
|
||||||
|
|
||||||
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
|
||||||
rect.offset(0, paddingTop);
|
|
||||||
|
|
||||||
drawGrid(activeCanvas, rect);
|
|
||||||
|
|
||||||
pText.setColor(textColor);
|
|
||||||
pGraph.setColor(primaryColor);
|
|
||||||
prevRect.setEmpty();
|
|
||||||
|
|
||||||
previousMonthText = "";
|
|
||||||
previousYearText = "";
|
|
||||||
skipYear = 0;
|
|
||||||
|
|
||||||
for (int k = 0; k < nColumns; k++)
|
|
||||||
{
|
|
||||||
int offset = nColumns - k - 1 + getDataOffset();
|
|
||||||
if (offset >= entries.size()) continue;
|
|
||||||
|
|
||||||
double value = entries.get(offset).getValue();
|
|
||||||
Timestamp timestamp = entries.get(offset).getTimestamp();
|
|
||||||
int height = (int) (columnHeight * value / maxValue);
|
|
||||||
|
|
||||||
rect.set(0, 0, baseSize, height);
|
|
||||||
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
|
||||||
paddingTop + columnHeight - height);
|
|
||||||
|
|
||||||
drawValue(activeCanvas, rect, value);
|
|
||||||
drawBar(activeCanvas, rect, value);
|
|
||||||
|
|
||||||
prevRect.set(rect);
|
|
||||||
rect.set(0, 0, columnWidth, columnHeight);
|
|
||||||
rect.offset(k * columnWidth, paddingTop);
|
|
||||||
|
|
||||||
drawFooter(activeCanvas, rect, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
||||||
{
|
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int width,
|
|
||||||
int height,
|
|
||||||
int oldWidth,
|
|
||||||
int oldHeight)
|
|
||||||
{
|
|
||||||
if (height < 9) height = 200;
|
|
||||||
|
|
||||||
float maxTextSize = getDimension(getContext(), R.dimen.tinyTextSize);
|
|
||||||
float textSize = height * 0.06f;
|
|
||||||
pText.setTextSize(Math.min(textSize, maxTextSize));
|
|
||||||
em = pText.getFontSpacing();
|
|
||||||
|
|
||||||
int footerHeight = (int) (3 * em);
|
|
||||||
paddingTop = (int) (em);
|
|
||||||
|
|
||||||
baseSize = (height - footerHeight - paddingTop) / 12;
|
|
||||||
columnWidth = baseSize;
|
|
||||||
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
|
|
||||||
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
|
|
||||||
|
|
||||||
nColumns = (int) (width / columnWidth);
|
|
||||||
columnWidth = (float) width / nColumns;
|
|
||||||
setScrollerBucketSize((int) columnWidth);
|
|
||||||
|
|
||||||
columnHeight = 12 * baseSize;
|
|
||||||
|
|
||||||
float minStrokeWidth = dpToPixels(getContext(), 1);
|
|
||||||
pGraph.setTextSize(baseSize * 0.5f);
|
|
||||||
pGraph.setStrokeWidth(baseSize * 0.1f);
|
|
||||||
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
|
|
||||||
|
|
||||||
if (isTransparencyEnabled) initCache(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawBar(Canvas canvas, RectF rect, double value)
|
|
||||||
{
|
|
||||||
float margin = baseSize * 0.225f;
|
|
||||||
float round = dpToPixels(getContext(), 2);
|
|
||||||
|
|
||||||
int color = primaryColor;
|
|
||||||
|
|
||||||
rect.inset(-margin, 0);
|
|
||||||
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
|
|
||||||
canvas.drawRoundRect(rect, round, round, pGraph);
|
|
||||||
|
|
||||||
rect.inset(margin, 0);
|
|
||||||
setModeOrColor(pGraph, XFERMODE_SRC, color);
|
|
||||||
canvas.drawRoundRect(rect, round, round, pGraph);
|
|
||||||
rect.set(rect.left,
|
|
||||||
rect.top + rect.height() / 2.0f,
|
|
||||||
rect.right,
|
|
||||||
rect.bottom);
|
|
||||||
canvas.drawRect(rect, pGraph);
|
|
||||||
|
|
||||||
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
|
|
||||||
{
|
|
||||||
String yearText = dfYear.format(currentDate.toJavaDate());
|
|
||||||
String monthText = dfMonth.format(currentDate.toJavaDate());
|
|
||||||
String dayText = dfDay.format(currentDate.toJavaDate());
|
|
||||||
|
|
||||||
GregorianCalendar calendar = currentDate.toCalendar();
|
|
||||||
pText.setColor(textColor);
|
|
||||||
|
|
||||||
String text;
|
|
||||||
int year = calendar.get(Calendar.YEAR);
|
|
||||||
|
|
||||||
boolean shouldPrintYear = true;
|
|
||||||
if (yearText.equals(previousYearText)) shouldPrintYear = false;
|
|
||||||
|
|
||||||
if (skipYear > 0)
|
|
||||||
{
|
|
||||||
skipYear--;
|
|
||||||
shouldPrintYear = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bucketSize >= 365) shouldPrintYear = true;
|
|
||||||
|
|
||||||
if (shouldPrintYear)
|
|
||||||
{
|
|
||||||
previousYearText = yearText;
|
|
||||||
previousMonthText = "";
|
|
||||||
|
|
||||||
pText.setTextAlign(Paint.Align.CENTER);
|
|
||||||
canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, pText);
|
|
||||||
skipYear = 1;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bucketSize < 365)
|
|
||||||
{
|
|
||||||
if (!monthText.equals(previousMonthText))
|
|
||||||
{
|
|
||||||
previousMonthText = monthText;
|
|
||||||
text = monthText;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = dayText;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f,
|
|
||||||
pText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawGrid(Canvas canvas, RectF rGrid)
|
|
||||||
{
|
|
||||||
int nRows = 5;
|
|
||||||
float rowHeight = rGrid.height() / nRows;
|
|
||||||
|
|
||||||
pText.setColor(textColor);
|
|
||||||
pGrid.setColor(gridColor);
|
|
||||||
|
|
||||||
for (int i = 0; i < nRows; i++)
|
|
||||||
{
|
|
||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
|
|
||||||
pGrid);
|
|
||||||
rGrid.offset(0, rowHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawValue(Canvas canvas, RectF rect, double value)
|
|
||||||
{
|
|
||||||
if (value == 0) return;
|
|
||||||
int activeColor = primaryColor;
|
|
||||||
|
|
||||||
String label = NumberButtonViewKt.toShortString(value / 1000);
|
|
||||||
Rect rText = new Rect();
|
|
||||||
pText.getTextBounds(label, 0, label.length(), rText);
|
|
||||||
|
|
||||||
float offset = 0.5f * em;
|
|
||||||
float x = rect.centerX();
|
|
||||||
float y = rect.top - offset;
|
|
||||||
int cap = (int) (-0.1f * em);
|
|
||||||
|
|
||||||
rText.offset((int) x, (int) y);
|
|
||||||
rText.offset(-rText.width() / 2, 0);
|
|
||||||
rText.inset(3 * cap, cap);
|
|
||||||
|
|
||||||
setModeOrColor(pText, XFERMODE_CLEAR, backgroundColor);
|
|
||||||
canvas.drawRect(rText, pText);
|
|
||||||
|
|
||||||
setModeOrColor(pText, XFERMODE_SRC, activeColor);
|
|
||||||
canvas.drawText(label, x, y, pText);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getMaxDayWidth()
|
|
||||||
{
|
|
||||||
float maxDayWidth = 0;
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendarWithOffset();
|
|
||||||
|
|
||||||
for (int i = 0; i < 28; i++)
|
|
||||||
{
|
|
||||||
day.set(Calendar.DAY_OF_MONTH, i);
|
|
||||||
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
|
||||||
maxDayWidth = Math.max(maxDayWidth, monthWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxDayWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getMaxMonthWidth()
|
|
||||||
{
|
|
||||||
float maxMonthWidth = 0;
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendarWithOffset();
|
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++)
|
|
||||||
{
|
|
||||||
day.set(Calendar.MONTH, i);
|
|
||||||
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
|
||||||
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxMonthWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
initPaints();
|
|
||||||
initColors();
|
|
||||||
initDateFormats();
|
|
||||||
initRects();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCache(int width, int height)
|
|
||||||
{
|
|
||||||
if (drawingCache != null) drawingCache.recycle();
|
|
||||||
drawingCache =
|
|
||||||
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
cacheCanvas = new Canvas(drawingCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initColors()
|
|
||||||
{
|
|
||||||
StyledResources res = new StyledResources(getContext());
|
|
||||||
|
|
||||||
primaryColor = Color.BLACK;
|
|
||||||
textColor = res.getColor(R.attr.mediumContrastTextColor);
|
|
||||||
gridColor = res.getColor(R.attr.lowContrastTextColor);
|
|
||||||
backgroundColor = res.getColor(R.attr.cardBgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDateFormats()
|
|
||||||
{
|
|
||||||
if (isInEditMode())
|
|
||||||
{
|
|
||||||
dfYear = new SimpleDateFormat("yyyy", Locale.US);
|
|
||||||
dfMonth = new SimpleDateFormat("MMM", Locale.US);
|
|
||||||
dfDay = new SimpleDateFormat("d", Locale.US);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dfYear = DateExtensionsKt.toSimpleDataFormat("yyyy");
|
|
||||||
dfMonth = DateExtensionsKt.toSimpleDataFormat("MMM");
|
|
||||||
dfDay = DateExtensionsKt.toSimpleDataFormat("d");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPaints()
|
|
||||||
{
|
|
||||||
pText = new Paint();
|
|
||||||
pText.setAntiAlias(true);
|
|
||||||
pText.setTextAlign(Paint.Align.CENTER);
|
|
||||||
|
|
||||||
pGraph = new Paint();
|
|
||||||
pGraph.setTextAlign(Paint.Align.CENTER);
|
|
||||||
pGraph.setAntiAlias(true);
|
|
||||||
|
|
||||||
pGrid = new Paint();
|
|
||||||
pGrid.setAntiAlias(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRects()
|
|
||||||
{
|
|
||||||
rect = new RectF();
|
|
||||||
prevRect = new RectF();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
|
||||||
{
|
|
||||||
if (isTransparencyEnabled) p.setXfermode(mode);
|
|
||||||
else p.setColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,13 +43,14 @@ import org.isoron.uhabits.intents.IntentFactory
|
|||||||
|
|
||||||
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
||||||
|
|
||||||
private val presenter = ShowHabitPresenter()
|
val presenter = ShowHabitPresenter()
|
||||||
|
|
||||||
private lateinit var commandRunner: CommandRunner
|
private lateinit var commandRunner: CommandRunner
|
||||||
private lateinit var menu: ShowHabitMenu
|
private lateinit var menu: ShowHabitMenu
|
||||||
private lateinit var view: ShowHabitView
|
private lateinit var view: ShowHabitView
|
||||||
private lateinit var habit: Habit
|
private lateinit var habit: Habit
|
||||||
private lateinit var preferences: Preferences
|
private lateinit var preferences: Preferences
|
||||||
|
private lateinit var themeSwitcher: AndroidThemeSwitcher
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
@@ -61,7 +62,8 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
|
habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
|
||||||
preferences = appComponent.preferences
|
preferences = appComponent.preferences
|
||||||
commandRunner = appComponent.commandRunner
|
commandRunner = appComponent.commandRunner
|
||||||
AndroidThemeSwitcher(this, preferences).apply()
|
themeSwitcher = AndroidThemeSwitcher(this, preferences)
|
||||||
|
themeSwitcher.apply()
|
||||||
|
|
||||||
view = ShowHabitView(this)
|
view = ShowHabitView(this)
|
||||||
|
|
||||||
@@ -134,6 +136,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
presenter.present(
|
presenter.present(
|
||||||
habit = habit,
|
habit = habit,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
|
theme = themeSwitcher.currentTheme,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardViewModel
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardViewModel
|
||||||
|
import org.isoron.uhabits.core.ui.views.BarChart
|
||||||
import org.isoron.uhabits.databinding.ShowHabitBarBinding
|
import org.isoron.uhabits.databinding.ShowHabitBarBinding
|
||||||
import org.isoron.uhabits.utils.toThemedAndroidColor
|
import org.isoron.uhabits.utils.toThemedAndroidColor
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
|
||||||
|
|
||||||
@@ -35,11 +38,16 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context,
|
|||||||
var onBoolSpinnerPosition: (position: Int) -> Unit = {}
|
var onBoolSpinnerPosition: (position: Int) -> Unit = {}
|
||||||
|
|
||||||
fun update(data: BarCardViewModel) {
|
fun update(data: BarCardViewModel) {
|
||||||
binding.barChart.setEntries(data.entries)
|
|
||||||
binding.barChart.setBucketSize(data.bucketSize)
|
|
||||||
val androidColor = data.color.toThemedAndroidColor(context)
|
val androidColor = data.color.toThemedAndroidColor(context)
|
||||||
|
binding.chart.view = BarChart(data.theme, JavaLocalDateFormatter(Locale.US)).apply {
|
||||||
|
series = mutableListOf(data.entries.map { it.value / 1000.0 })
|
||||||
|
colors = mutableListOf(theme.color(data.color.paletteIndex))
|
||||||
|
axis = data.entries.map { it.timestamp.toLocalDate() }
|
||||||
|
}
|
||||||
|
binding.chart.resetDataOffset()
|
||||||
|
binding.chart.postInvalidate()
|
||||||
|
|
||||||
binding.title.setTextColor(androidColor)
|
binding.title.setTextColor(androidColor)
|
||||||
binding.barChart.setColor(androidColor)
|
|
||||||
if (data.isNumerical) {
|
if (data.isNumerical) {
|
||||||
binding.boolSpinner.visibility = GONE
|
binding.boolSpinner.visibility = GONE
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
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">
|
||||||
|
|
||||||
<org.isoron.platform.gui.AndroidCanvasTestView
|
<org.isoron.platform.gui.AndroidTestView
|
||||||
android:layout_width="500dp"
|
android:layout_width="500dp"
|
||||||
android:layout_height="400dp" />
|
android:layout_height="400dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:text="@string/history" />
|
android:text="@string/history" />
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.common.views.BarChart
|
<org.isoron.platform.gui.AndroidDataView
|
||||||
android:id="@+id/barChart"
|
android:id="@+id/chart"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="220dp"
|
android:layout_height="220dp"
|
||||||
android:layout_below="@id/title"/>
|
android:layout_below="@id/title"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 424 B |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
package org.isoron.platform.gui
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
interface Component {
|
interface View {
|
||||||
fun draw(canvas: Canvas)
|
fun draw(canvas: Canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DataView : View {
|
||||||
|
var dataOffset: Int
|
||||||
|
val dataColumnWidth: Double
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.platform.time
|
package org.isoron.platform.time
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
@@ -32,15 +33,6 @@ enum class DayOfWeek(val index: Int) {
|
|||||||
SATURDAY(6),
|
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) {
|
data class LocalDate(val daysSince2000: Int) {
|
||||||
|
|
||||||
var yearCache = -1
|
var yearCache = -1
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
package org.isoron.uhabits.core.models;
|
package org.isoron.uhabits.core.models;
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.*;
|
import org.apache.commons.lang3.builder.*;
|
||||||
|
import org.isoron.platform.time.LocalDate;
|
||||||
import org.isoron.uhabits.core.utils.*;
|
import org.isoron.uhabits.core.utils.*;
|
||||||
import org.jetbrains.annotations.*;
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
import java.time.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import kotlin.*;
|
import kotlin.*;
|
||||||
@@ -66,6 +68,13 @@ public final class Timestamp implements Comparable<Timestamp>
|
|||||||
return unixTime;
|
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
|
* Returns -1 if this timestamp is older than the given timestamp, 1 if this
|
||||||
* timestamp is newer, or zero if they are equal.
|
* timestamp is newer, or zero if they are equal.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui;
|
|||||||
import androidx.annotation.*;
|
import androidx.annotation.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
import org.isoron.uhabits.core.preferences.*;
|
||||||
|
import org.isoron.uhabits.core.ui.views.*;
|
||||||
|
|
||||||
public abstract class ThemeSwitcher
|
public abstract class ThemeSwitcher
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,8 @@ public abstract class ThemeSwitcher
|
|||||||
|
|
||||||
public abstract int getSystemTheme();
|
public abstract int getSystemTheme();
|
||||||
|
|
||||||
|
public abstract Theme getCurrentTheme();
|
||||||
|
|
||||||
public boolean isNightMode()
|
public boolean isNightMode()
|
||||||
{
|
{
|
||||||
int systemTheme = getSystemTheme();
|
int systemTheme = getSystemTheme();
|
||||||
|
|||||||
@@ -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.SubtitleCardViewModel
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
|
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.screens.habits.show.views.TargetCardViewModel
|
||||||
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
|
|
||||||
data class ShowHabitViewModel(
|
data class ShowHabitViewModel(
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
@@ -60,6 +61,7 @@ class ShowHabitPresenter {
|
|||||||
fun present(
|
fun present(
|
||||||
habit: Habit,
|
habit: Habit,
|
||||||
preferences: Preferences,
|
preferences: Preferences,
|
||||||
|
theme: Theme,
|
||||||
): ShowHabitViewModel {
|
): ShowHabitViewModel {
|
||||||
return ShowHabitViewModel(
|
return ShowHabitViewModel(
|
||||||
title = habit.name,
|
title = habit.name,
|
||||||
@@ -100,6 +102,7 @@ class ShowHabitPresenter {
|
|||||||
firstWeekday = preferences.firstWeekday,
|
firstWeekday = preferences.firstWeekday,
|
||||||
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
|
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
|
||||||
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
|
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
|
||||||
|
theme = theme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import org.isoron.uhabits.core.models.Entry
|
|||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.groupedSum
|
import org.isoron.uhabits.core.models.groupedSum
|
||||||
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
|
||||||
data class BarCardViewModel(
|
data class BarCardViewModel(
|
||||||
|
val theme: Theme,
|
||||||
val boolSpinnerPosition: Int,
|
val boolSpinnerPosition: Int,
|
||||||
val bucketSize: Int,
|
val bucketSize: Int,
|
||||||
val color: PaletteColor,
|
val color: PaletteColor,
|
||||||
@@ -43,6 +45,7 @@ class BarCardPresenter {
|
|||||||
firstWeekday: Int,
|
firstWeekday: Int,
|
||||||
numericalSpinnerPosition: Int,
|
numericalSpinnerPosition: Int,
|
||||||
boolSpinnerPosition: Int,
|
boolSpinnerPosition: Int,
|
||||||
|
theme: Theme,
|
||||||
): BarCardViewModel {
|
): BarCardViewModel {
|
||||||
val bucketSize = if (habit.isNumerical) {
|
val bucketSize = if (habit.isNumerical) {
|
||||||
numericalBucketSizes[numericalSpinnerPosition]
|
numericalBucketSizes[numericalSpinnerPosition]
|
||||||
@@ -57,6 +60,7 @@ class BarCardPresenter {
|
|||||||
isNumerical = habit.isNumerical,
|
isNumerical = habit.isNumerical,
|
||||||
)
|
)
|
||||||
return BarCardViewModel(
|
return BarCardViewModel(
|
||||||
|
theme = theme,
|
||||||
entries = entries,
|
entries = entries,
|
||||||
bucketSize = bucketSize,
|
bucketSize = bucketSize,
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Color
|
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.gui.TextAlign
|
||||||
import org.isoron.platform.time.LocalDate
|
import org.isoron.platform.time.LocalDate
|
||||||
import org.isoron.platform.time.LocalDateFormatter
|
import org.isoron.platform.time.LocalDateFormatter
|
||||||
@@ -32,12 +32,13 @@ import kotlin.math.round
|
|||||||
class BarChart(
|
class BarChart(
|
||||||
var theme: Theme,
|
var theme: Theme,
|
||||||
var dateFormatter: LocalDateFormatter,
|
var dateFormatter: LocalDateFormatter,
|
||||||
) : Component {
|
) : DataView {
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
var series = mutableListOf<List<Double>>()
|
var series = mutableListOf<List<Double>>()
|
||||||
var colors = mutableListOf<Color>()
|
var colors = mutableListOf<Color>()
|
||||||
var axis = listOf<LocalDate>()
|
var axis = listOf<LocalDate>()
|
||||||
|
override var dataOffset = 0
|
||||||
|
|
||||||
// Style
|
// Style
|
||||||
var paddingTop = 20.0
|
var paddingTop = 20.0
|
||||||
@@ -50,6 +51,9 @@ class BarChart(
|
|||||||
var nGridlines = 6
|
var nGridlines = 6
|
||||||
var backgroundColor = theme.cardBackgroundColor
|
var backgroundColor = theme.cardBackgroundColor
|
||||||
|
|
||||||
|
override val dataColumnWidth: Double
|
||||||
|
get() = barWidth + barMargin * 2
|
||||||
|
|
||||||
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()
|
||||||
@@ -75,8 +79,11 @@ class BarChart(
|
|||||||
barMargin
|
barMargin
|
||||||
|
|
||||||
fun drawColumn(s: Int, c: Int) {
|
fun drawColumn(s: Int, c: Int) {
|
||||||
val dataColumn = nColumns - c - 1
|
val dataColumn = nColumns - c - 1 + dataOffset
|
||||||
val value = if (dataColumn < series[s].size) series[s][dataColumn] else 0.0
|
val value = when {
|
||||||
|
dataColumn < 0 || dataColumn >= series[s].size -> 0.0
|
||||||
|
else -> series[s][dataColumn]
|
||||||
|
}
|
||||||
if (value <= 0) return
|
if (value <= 0) return
|
||||||
val perc = value / maxValue
|
val perc = value / maxValue
|
||||||
val barHeight = round(maxBarHeight * perc)
|
val barHeight = round(maxBarHeight * perc)
|
||||||
@@ -142,12 +149,12 @@ class BarChart(
|
|||||||
canvas.setTextAlign(TextAlign.CENTER)
|
canvas.setTextAlign(TextAlign.CENTER)
|
||||||
var prevMonth = -1
|
var prevMonth = -1
|
||||||
var prevYear = -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) {
|
for (c in 0 until nColumns) {
|
||||||
val x = barGroupOffset(c)
|
val x = barGroupOffset(c)
|
||||||
val dataColumn = nColumns - c - 1
|
val dataColumn = nColumns - c - 1 + dataOffset
|
||||||
if (dataColumn >= axis.size) continue
|
if (dataColumn < 0 || dataColumn >= axis.size) continue
|
||||||
val date = axis[dataColumn]
|
val date = axis[dataColumn]
|
||||||
if (isLargeInterval) {
|
if (isLargeInterval) {
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Color
|
import org.isoron.platform.gui.Color
|
||||||
import org.isoron.platform.gui.Component
|
|
||||||
import org.isoron.platform.gui.TextAlign
|
import org.isoron.platform.gui.TextAlign
|
||||||
|
import org.isoron.platform.gui.View
|
||||||
import org.isoron.platform.time.LocalDate
|
import org.isoron.platform.time.LocalDate
|
||||||
import org.isoron.platform.time.LocalDateFormatter
|
import org.isoron.platform.time.LocalDateFormatter
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
@@ -33,7 +33,7 @@ class CalendarChart(
|
|||||||
var color: Color,
|
var color: Color,
|
||||||
var theme: Theme,
|
var theme: Theme,
|
||||||
var dateFormatter: LocalDateFormatter
|
var dateFormatter: LocalDateFormatter
|
||||||
) : Component {
|
) : View {
|
||||||
|
|
||||||
var padding = 5.0
|
var padding = 5.0
|
||||||
var backgroundColor = Color(0xFFFFFF)
|
var backgroundColor = Color(0xFFFFFF)
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Color
|
import org.isoron.platform.gui.Color
|
||||||
import org.isoron.platform.gui.Component
|
|
||||||
import org.isoron.platform.gui.Font
|
import org.isoron.platform.gui.Font
|
||||||
import org.isoron.platform.gui.FontAwesome
|
import org.isoron.platform.gui.FontAwesome
|
||||||
|
import org.isoron.platform.gui.View
|
||||||
|
|
||||||
class CheckmarkButton(
|
class CheckmarkButton(
|
||||||
private val value: Int,
|
private val value: Int,
|
||||||
private val color: Color,
|
private val color: Color,
|
||||||
private val theme: Theme
|
private val theme: Theme
|
||||||
) : Component {
|
) : View {
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
canvas.setFont(Font.FONT_AWESOME)
|
canvas.setFont(Font.FONT_AWESOME)
|
||||||
canvas.setFontSize(theme.smallTextSize * 1.5)
|
canvas.setFontSize(theme.smallTextSize * 1.5)
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
package org.isoron.uhabits.core.ui.views
|
package org.isoron.uhabits.core.ui.views
|
||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Component
|
|
||||||
import org.isoron.platform.gui.Font
|
import org.isoron.platform.gui.Font
|
||||||
|
import org.isoron.platform.gui.View
|
||||||
import org.isoron.platform.time.LocalDate
|
import org.isoron.platform.time.LocalDate
|
||||||
import org.isoron.platform.time.LocalDateFormatter
|
import org.isoron.platform.time.LocalDateFormatter
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class HabitListHeader(
|
|||||||
private val nButtons: Int,
|
private val nButtons: Int,
|
||||||
private val theme: Theme,
|
private val theme: Theme,
|
||||||
private val fmt: LocalDateFormatter
|
private val fmt: LocalDateFormatter
|
||||||
) : Component {
|
) : View {
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
val width = canvas.getWidth()
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Color
|
import org.isoron.platform.gui.Color
|
||||||
import org.isoron.platform.gui.Component
|
|
||||||
import org.isoron.platform.gui.Font
|
import org.isoron.platform.gui.Font
|
||||||
|
import org.isoron.platform.gui.View
|
||||||
import java.lang.String.format
|
import java.lang.String.format
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class NumberButton(
|
|||||||
val threshold: Double,
|
val threshold: Double,
|
||||||
val units: String,
|
val units: String,
|
||||||
val theme: Theme
|
val theme: Theme
|
||||||
) : Component {
|
) : View {
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
val width = canvas.getWidth()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
|
|
||||||
import org.isoron.platform.gui.Canvas
|
import org.isoron.platform.gui.Canvas
|
||||||
import org.isoron.platform.gui.Color
|
import org.isoron.platform.gui.Color
|
||||||
import org.isoron.platform.gui.Component
|
import org.isoron.platform.gui.View
|
||||||
import java.lang.String.format
|
import java.lang.String.format
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -33,7 +33,7 @@ class Ring(
|
|||||||
val radius: Double,
|
val radius: Double,
|
||||||
val theme: Theme,
|
val theme: Theme,
|
||||||
val label: Boolean = false
|
val label: Boolean = false
|
||||||
) : Component {
|
) : View {
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.getWidth()
|
val width = canvas.getWidth()
|
||||||
|
|||||||
@@ -22,27 +22,23 @@ package org.isoron.uhabits.core.ui.views
|
|||||||
import org.isoron.platform.gui.Color
|
import org.isoron.platform.gui.Color
|
||||||
|
|
||||||
abstract class Theme {
|
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)
|
open fun color(paletteIndex: Int): Color {
|
||||||
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 {
|
|
||||||
return when (paletteIndex) {
|
return when (paletteIndex) {
|
||||||
0 -> Color(0xD32F2F)
|
0 -> Color(0xD32F2F)
|
||||||
1 -> Color(0x512DA8)
|
1 -> Color(0xE64A19)
|
||||||
2 -> Color(0xF57C00)
|
2 -> Color(0xF57C00)
|
||||||
3 -> Color(0xFF8F00)
|
3 -> Color(0xFF8F00)
|
||||||
4 -> Color(0xF9A825)
|
4 -> Color(0xF9A825)
|
||||||
@@ -58,6 +54,9 @@ abstract class Theme {
|
|||||||
14 -> Color(0x8E24AA)
|
14 -> Color(0x8E24AA)
|
||||||
15 -> Color(0xD81B60)
|
15 -> Color(0xD81B60)
|
||||||
16 -> Color(0x5D4037)
|
16 -> Color(0x5D4037)
|
||||||
|
17 -> Color(0x424242)
|
||||||
|
18 -> Color(0x757575)
|
||||||
|
19 -> Color(0x9E9E9E)
|
||||||
else -> Color(0x000000)
|
else -> Color(0x000000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,3 +67,44 @@ abstract class Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LightTheme : 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import java.awt.image.BufferedImage.TYPE_INT_ARGB
|
|||||||
class JavaCanvasTest {
|
class JavaCanvasTest {
|
||||||
@Test
|
@Test
|
||||||
fun run() = runBlocking {
|
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,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
expectedPath: String,
|
expectedPath: String,
|
||||||
component: Component,
|
view: View,
|
||||||
) {
|
) {
|
||||||
val canvas = createCanvas(width, height)
|
val canvas = createCanvas(width, height)
|
||||||
component.draw(canvas)
|
view.draw(canvas)
|
||||||
assertRenders(expectedPath, canvas)
|
assertRenders(expectedPath, canvas)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,17 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 kotlinx.coroutines.runBlocking
|
||||||
import org.isoron.platform.gui.assertRenders
|
import org.isoron.platform.gui.assertRenders
|
||||||
import org.isoron.platform.time.JavaLocalDateFormatter
|
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||||
import org.isoron.platform.time.LocalDate
|
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 org.junit.Test
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class BarChartTest {
|
class BarChartTest {
|
||||||
val base = "components/BarChart"
|
val base = "views/BarChart"
|
||||||
val today = LocalDate(2015, 1, 25)
|
val today = LocalDate(2015, 1, 25)
|
||||||
val fmt = JavaLocalDateFormatter(Locale.US)
|
val fmt = JavaLocalDateFormatter(Locale.US)
|
||||||
val theme = LightTheme()
|
val theme = LightTheme()
|
||||||
@@ -37,11 +35,20 @@ class BarChartTest {
|
|||||||
val axis = (0..100).map { today.minus(it) }
|
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)
|
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
|
init {
|
||||||
fun testDraw() = runBlocking {
|
|
||||||
component.axis = axis
|
component.axis = axis
|
||||||
component.series.add(series1)
|
component.series.add(series1)
|
||||||
component.colors.add(theme.color(8))
|
component.colors.add(theme.color(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDraw() = runBlocking {
|
||||||
assertRenders(300, 200, "$base/base.png", component)
|
assertRenders(300, 200, "$base/base.png", component)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDrawWithOffset() = runBlocking {
|
||||||
|
component.dataOffset = 5
|
||||||
|
assertRenders(300, 200, "$base/offset.png", component)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||