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

View File

@@ -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");
}
}

View File

@@ -25,8 +25,6 @@ import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
class AndroidCanvas : Canvas {
@@ -107,7 +105,7 @@ class AndroidCanvas : Canvas {
}
override fun setFontSize(size: Double) {
textPaint.textSize = size.toDp() * 1.07f
textPaint.textSize = size.toDp()
}
override fun setStrokeWidth(size: Double) {
@@ -156,14 +154,3 @@ class AndroidCanvas : Canvas {
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()
}
}

View File

@@ -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()
}
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -28,6 +28,9 @@ import androidx.core.content.ContextCompat
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
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.ActivityScope
@@ -35,9 +38,11 @@ import org.isoron.uhabits.inject.ActivityScope
class AndroidThemeSwitcher
constructor(
@ActivityContext val context: Context,
preferences: Preferences
preferences: Preferences,
) : ThemeSwitcher(preferences) {
private var currentTheme: Theme = LightTheme()
override fun getSystemTheme(): Int {
if (SDK_INT < 29) return THEME_LIGHT
val uiMode = context.resources.configuration.uiMode
@@ -48,17 +53,24 @@ constructor(
}
}
override fun getCurrentTheme(): Theme {
return currentTheme
}
override fun applyDarkTheme() {
currentTheme = DarkTheme()
context.setTheme(R.style.AppBaseThemeDark)
(context as Activity).window.navigationBarColor =
ContextCompat.getColor(context, R.color.grey_900)
}
override fun applyLightTheme() {
currentTheme = LightTheme()
context.setTheme(R.style.AppBaseTheme)
}
override fun applyPureBlackTheme() {
currentTheme = DarkTheme()
context.setTheme(R.style.AppBaseThemeDark_PureBlack)
(context as Activity).window.navigationBarColor =
ContextCompat.getColor(context, R.color.black)

View File

@@ -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);
}
}

View File

@@ -43,13 +43,14 @@ import org.isoron.uhabits.intents.IntentFactory
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
private val presenter = ShowHabitPresenter()
val presenter = ShowHabitPresenter()
private lateinit var commandRunner: CommandRunner
private lateinit var menu: ShowHabitMenu
private lateinit var view: ShowHabitView
private lateinit var habit: Habit
private lateinit var preferences: Preferences
private lateinit var themeSwitcher: AndroidThemeSwitcher
private val scope = CoroutineScope(Dispatchers.Main)
@@ -61,7 +62,8 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
preferences = appComponent.preferences
commandRunner = appComponent.commandRunner
AndroidThemeSwitcher(this, preferences).apply()
themeSwitcher = AndroidThemeSwitcher(this, preferences)
themeSwitcher.apply()
view = ShowHabitView(this)
@@ -134,6 +136,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
presenter.present(
habit = habit,
preferences = preferences,
theme = themeSwitcher.currentTheme,
)
)
}

View File

@@ -24,9 +24,12 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
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.views.BarChart
import org.isoron.uhabits.databinding.ShowHabitBarBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
import java.util.Locale
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 = {}
fun update(data: BarCardViewModel) {
binding.barChart.setEntries(data.entries)
binding.barChart.setBucketSize(data.bucketSize)
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.barChart.setColor(androidColor)
if (data.isNumerical) {
binding.boolSpinner.visibility = GONE
} else {

View File

@@ -22,7 +22,7 @@
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<org.isoron.platform.gui.AndroidCanvasTestView
<org.isoron.platform.gui.AndroidTestView
android:layout_width="500dp"
android:layout_height="400dp" />
</LinearLayout>

View File

@@ -52,8 +52,8 @@
android:layout_alignParentTop="true"
android:text="@string/history" />
<org.isoron.uhabits.activities.common.views.BarChart
android:id="@+id/barChart"
<org.isoron.platform.gui.AndroidDataView
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_below="@id/title"/>