diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/BarChartTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/BarChartTest.java deleted file mode 100644 index e78e4f0cb..000000000 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/BarChartTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -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"); - } -} diff --git a/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidCanvas.kt b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidCanvas.kt index 6cd92fb9a..9d9c38743 100644 --- a/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidCanvas.kt +++ b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidCanvas.kt @@ -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() - } -} diff --git a/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt new file mode 100644 index 000000000..ec4ba4dc7 --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016-2020 Álinson Santos Xavier + * + * 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 . + */ + +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(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() + } + } +} diff --git a/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidTestView.kt b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidTestView.kt new file mode 100644 index 000000000..9f7a532de --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidTestView.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016-2020 Álinson Santos Xavier + * + * 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 . + */ + +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() + } +} diff --git a/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidView.kt b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidView.kt new file mode 100644 index 000000000..9709e2fc1 --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidView.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016-2020 Álinson Santos Xavier + * + * 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 . + */ + +package org.isoron.platform.gui + +import android.content.Context +import android.util.AttributeSet + +open class AndroidView( + 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) + } +} diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt index 6b7779534..3ce23e4fa 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/AndroidThemeSwitcher.kt @@ -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) diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java deleted file mode 100644 index eca566793..000000000 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -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 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 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 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); - } -} diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt index 9b22bdfef..75f3145d7 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt @@ -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, ) ) } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt index 44c7a40f2..dfac3233b 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt @@ -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 { diff --git a/android/uhabits-android/src/main/res/layout/canvas_test.xml b/android/uhabits-android/src/main/res/layout/canvas_test.xml index 2d2cee167..6faa0df2e 100644 --- a/android/uhabits-android/src/main/res/layout/canvas_test.xml +++ b/android/uhabits-android/src/main/res/layout/canvas_test.xml @@ -22,7 +22,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> - \ No newline at end of file diff --git a/android/uhabits-android/src/main/res/layout/show_habit_bar.xml b/android/uhabits-android/src/main/res/layout/show_habit_bar.xml index 5377a35ba..f696e30e3 100644 --- a/android/uhabits-android/src/main/res/layout/show_habit_bar.xml +++ b/android/uhabits-android/src/main/res/layout/show_habit_bar.xml @@ -52,8 +52,8 @@ android:layout_alignParentTop="true" android:text="@string/history" /> - diff --git a/android/uhabits-core/assets/test/components/BarChart/base.png b/android/uhabits-core/assets/test/components/BarChart/base.png deleted file mode 100644 index 4bb551c99..000000000 Binary files a/android/uhabits-core/assets/test/components/BarChart/base.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CalendarChart/base.png b/android/uhabits-core/assets/test/components/CalendarChart/base.png deleted file mode 100644 index 762877164..000000000 Binary files a/android/uhabits-core/assets/test/components/CalendarChart/base.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CalendarChart/scroll.png b/android/uhabits-core/assets/test/components/CalendarChart/scroll.png deleted file mode 100644 index 16744a297..000000000 Binary files a/android/uhabits-core/assets/test/components/CalendarChart/scroll.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CanvasTest.png b/android/uhabits-core/assets/test/components/CanvasTest.png deleted file mode 100644 index 75b3d8f20..000000000 Binary files a/android/uhabits-core/assets/test/components/CanvasTest.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CheckmarkButton/explicit.png b/android/uhabits-core/assets/test/components/CheckmarkButton/explicit.png deleted file mode 100644 index c2b66f74e..000000000 Binary files a/android/uhabits-core/assets/test/components/CheckmarkButton/explicit.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CheckmarkButton/implicit.png b/android/uhabits-core/assets/test/components/CheckmarkButton/implicit.png deleted file mode 100644 index acc067024..000000000 Binary files a/android/uhabits-core/assets/test/components/CheckmarkButton/implicit.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/CheckmarkButton/unchecked.png b/android/uhabits-core/assets/test/components/CheckmarkButton/unchecked.png deleted file mode 100644 index 283f32d40..000000000 Binary files a/android/uhabits-core/assets/test/components/CheckmarkButton/unchecked.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/HabitListHeader/light.png b/android/uhabits-core/assets/test/components/HabitListHeader/light.png deleted file mode 100644 index c146b916d..000000000 Binary files a/android/uhabits-core/assets/test/components/HabitListHeader/light.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/NumberButton/render_above.png b/android/uhabits-core/assets/test/components/NumberButton/render_above.png deleted file mode 100644 index 4673ef0f3..000000000 Binary files a/android/uhabits-core/assets/test/components/NumberButton/render_above.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/NumberButton/render_below.png b/android/uhabits-core/assets/test/components/NumberButton/render_below.png deleted file mode 100644 index c3ffb68de..000000000 Binary files a/android/uhabits-core/assets/test/components/NumberButton/render_below.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/NumberButton/render_zero.png b/android/uhabits-core/assets/test/components/NumberButton/render_zero.png deleted file mode 100644 index 235de2f62..000000000 Binary files a/android/uhabits-core/assets/test/components/NumberButton/render_zero.png and /dev/null differ diff --git a/android/uhabits-core/assets/test/components/Ring/draw1.png b/android/uhabits-core/assets/test/components/Ring/draw1.png deleted file mode 100644 index d5e80f455..000000000 Binary files a/android/uhabits-core/assets/test/components/Ring/draw1.png and /dev/null differ diff --git a/android/uhabits-core/src/main/java/org/isoron/platform/gui/Component.kt b/android/uhabits-core/src/main/java/org/isoron/platform/gui/View.kt similarity index 89% rename from android/uhabits-core/src/main/java/org/isoron/platform/gui/Component.kt rename to android/uhabits-core/src/main/java/org/isoron/platform/gui/View.kt index 8572f048e..dc775cc7f 100644 --- a/android/uhabits-core/src/main/java/org/isoron/platform/gui/Component.kt +++ b/android/uhabits-core/src/main/java/org/isoron/platform/gui/View.kt @@ -19,6 +19,11 @@ package org.isoron.platform.gui -interface Component { +interface View { fun draw(canvas: Canvas) } + +interface DataView : View { + var dataOffset: Int + val dataColumnWidth: Double +} diff --git a/android/uhabits-core/src/main/java/org/isoron/platform/time/Dates.kt b/android/uhabits-core/src/main/java/org/isoron/platform/time/Dates.kt index 65bb1e3e7..d7ac92f3b 100644 --- a/android/uhabits-core/src/main/java/org/isoron/platform/time/Dates.kt +++ b/android/uhabits-core/src/main/java/org/isoron/platform/time/Dates.kt @@ -19,6 +19,7 @@ package org.isoron.platform.time +import org.isoron.uhabits.core.models.Timestamp import kotlin.math.abs import kotlin.math.ceil @@ -32,15 +33,6 @@ enum class DayOfWeek(val index: Int) { SATURDAY(6), } -data class Timestamp(val millisSince1970: Long) { - val localDate: LocalDate - get() { - val millisSince2000 = millisSince1970 - 946684800000 - val daysSince2000 = millisSince2000 / 86400000 - return LocalDate(daysSince2000.toInt()) - } -} - data class LocalDate(val daysSince2000: Int) { var yearCache = -1 diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java index 45a3b387b..8abf9ef3f 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java @@ -20,9 +20,11 @@ package org.isoron.uhabits.core.models; import org.apache.commons.lang3.builder.*; +import org.isoron.platform.time.LocalDate; import org.isoron.uhabits.core.utils.*; import org.jetbrains.annotations.*; +import java.time.*; import java.util.*; import kotlin.*; @@ -66,6 +68,13 @@ public final class Timestamp implements Comparable return unixTime; } + public LocalDate toLocalDate() + { + long millisSince2000 = unixTime - 946684800000L; + int daysSince2000 = (int) (millisSince2000 / 86400000); + return new LocalDate(daysSince2000); + } + /** * Returns -1 if this timestamp is older than the given timestamp, 1 if this * timestamp is newer, or zero if they are equal. diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/ThemeSwitcher.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/ThemeSwitcher.java index e0e35a739..7c83130fa 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/ThemeSwitcher.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/ThemeSwitcher.java @@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui; import androidx.annotation.*; import org.isoron.uhabits.core.preferences.*; +import org.isoron.uhabits.core.ui.views.*; public abstract class ThemeSwitcher { @@ -59,6 +60,8 @@ public abstract class ThemeSwitcher public abstract int getSystemTheme(); + public abstract Theme getCurrentTheme(); + public boolean isNightMode() { int systemTheme = getSystemTheme(); diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabit.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabit.kt index 72cce2936..bbd8731be 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabit.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabit.kt @@ -40,6 +40,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardPresente import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardViewModel import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardViewModel +import org.isoron.uhabits.core.ui.views.Theme data class ShowHabitViewModel( val title: String = "", @@ -60,6 +61,7 @@ class ShowHabitPresenter { fun present( habit: Habit, preferences: Preferences, + theme: Theme, ): ShowHabitViewModel { return ShowHabitViewModel( title = habit.name, @@ -100,6 +102,7 @@ class ShowHabitPresenter { firstWeekday = preferences.firstWeekday, boolSpinnerPosition = preferences.barCardBoolSpinnerPosition, numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition, + theme = theme, ), ) } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt index e91ded06f..1abad408d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt @@ -23,9 +23,11 @@ import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.groupedSum +import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.utils.DateUtils data class BarCardViewModel( + val theme: Theme, val boolSpinnerPosition: Int, val bucketSize: Int, val color: PaletteColor, @@ -43,6 +45,7 @@ class BarCardPresenter { firstWeekday: Int, numericalSpinnerPosition: Int, boolSpinnerPosition: Int, + theme: Theme, ): BarCardViewModel { val bucketSize = if (habit.isNumerical) { numericalBucketSizes[numericalSpinnerPosition] @@ -57,6 +60,7 @@ class BarCardPresenter { isNumerical = habit.isNumerical, ) return BarCardViewModel( + theme = theme, entries = entries, bucketSize = bucketSize, color = habit.color, diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/BarChart.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/BarChart.kt index 638a92a0e..533b3a69e 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/BarChart.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/BarChart.kt @@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Color -import org.isoron.platform.gui.Component +import org.isoron.platform.gui.DataView import org.isoron.platform.gui.TextAlign import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDateFormatter @@ -32,12 +32,13 @@ import kotlin.math.round class BarChart( var theme: Theme, var dateFormatter: LocalDateFormatter, -) : Component { +) : DataView { // Data var series = mutableListOf>() var colors = mutableListOf() var axis = listOf() + override var dataOffset = 0 // Style var paddingTop = 20.0 @@ -50,6 +51,9 @@ class BarChart( var nGridlines = 6 var backgroundColor = theme.cardBackgroundColor + override val dataColumnWidth: Double + get() = barWidth + barMargin * 2 + override fun draw(canvas: Canvas) { val width = canvas.getWidth() val height = canvas.getHeight() @@ -75,8 +79,11 @@ class BarChart( barMargin fun drawColumn(s: Int, c: Int) { - val dataColumn = nColumns - c - 1 - val value = if (dataColumn < series[s].size) series[s][dataColumn] else 0.0 + val dataColumn = nColumns - c - 1 + dataOffset + val value = when { + dataColumn < 0 || dataColumn >= series[s].size -> 0.0 + else -> series[s][dataColumn] + } if (value <= 0) return val perc = value / maxValue val barHeight = round(maxBarHeight * perc) @@ -142,12 +149,12 @@ class BarChart( canvas.setTextAlign(TextAlign.CENTER) var prevMonth = -1 var prevYear = -1 - val isLargeInterval = (axis[0].distanceTo(axis[1]) > 300) + val isLargeInterval = axis.size < 2 || (axis[0].distanceTo(axis[1]) > 300) for (c in 0 until nColumns) { val x = barGroupOffset(c) - val dataColumn = nColumns - c - 1 - if (dataColumn >= axis.size) continue + val dataColumn = nColumns - c - 1 + dataOffset + if (dataColumn < 0 || dataColumn >= axis.size) continue val date = axis[dataColumn] if (isLargeInterval) { canvas.drawText( diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CalendarChart.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CalendarChart.kt index 2555e0d9e..a5f7d6bd1 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CalendarChart.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CalendarChart.kt @@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Color -import org.isoron.platform.gui.Component import org.isoron.platform.gui.TextAlign +import org.isoron.platform.gui.View import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDateFormatter import kotlin.math.floor @@ -33,7 +33,7 @@ class CalendarChart( var color: Color, var theme: Theme, var dateFormatter: LocalDateFormatter -) : Component { +) : View { var padding = 5.0 var backgroundColor = Color(0xFFFFFF) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CheckmarkButton.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CheckmarkButton.kt index 3de5d4bb1..a5b28407e 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CheckmarkButton.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/CheckmarkButton.kt @@ -21,15 +21,15 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Color -import org.isoron.platform.gui.Component import org.isoron.platform.gui.Font import org.isoron.platform.gui.FontAwesome +import org.isoron.platform.gui.View class CheckmarkButton( private val value: Int, private val color: Color, private val theme: Theme -) : Component { +) : View { override fun draw(canvas: Canvas) { canvas.setFont(Font.FONT_AWESOME) canvas.setFontSize(theme.smallTextSize * 1.5) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/HabitListHeader.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/HabitListHeader.kt index 703c86a6d..8ba3d1435 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/HabitListHeader.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/HabitListHeader.kt @@ -20,8 +20,8 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas -import org.isoron.platform.gui.Component import org.isoron.platform.gui.Font +import org.isoron.platform.gui.View import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDateFormatter @@ -30,7 +30,7 @@ class HabitListHeader( private val nButtons: Int, private val theme: Theme, private val fmt: LocalDateFormatter -) : Component { +) : View { override fun draw(canvas: Canvas) { val width = canvas.getWidth() diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/NumberButton.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/NumberButton.kt index d991a79bd..2d645bca6 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/NumberButton.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/NumberButton.kt @@ -21,8 +21,8 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Color -import org.isoron.platform.gui.Component import org.isoron.platform.gui.Font +import org.isoron.platform.gui.View import java.lang.String.format import kotlin.math.round @@ -52,7 +52,7 @@ class NumberButton( val threshold: Double, val units: String, val theme: Theme -) : Component { +) : View { override fun draw(canvas: Canvas) { val width = canvas.getWidth() diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Ring.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Ring.kt index 285f09139..3b8d022ee 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Ring.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Ring.kt @@ -21,7 +21,7 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Color -import org.isoron.platform.gui.Component +import org.isoron.platform.gui.View import java.lang.String.format import kotlin.math.max import kotlin.math.min @@ -33,7 +33,7 @@ class Ring( val radius: Double, val theme: Theme, val label: Boolean = false -) : Component { +) : View { override fun draw(canvas: Canvas) { val width = canvas.getWidth() diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Themes.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Themes.kt index 449beeba0..525977858 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Themes.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/views/Themes.kt @@ -22,27 +22,23 @@ package org.isoron.uhabits.core.ui.views import org.isoron.platform.gui.Color abstract class Theme { - val toolbarColor = Color(0xffffff) + open val appBackgroundColor = Color(0xf4f4f4) + open val cardBackgroundColor = Color(0xFAFAFA) + open val headerBackgroundColor = Color(0xeeeeee) + open val headerBorderColor = Color(0xcccccc) + open val headerTextColor = Color(0x9E9E9E) + open val highContrastTextColor = Color(0x202020) + open val itemBackgroundColor = Color(0xffffff) + open val lowContrastTextColor = Color(0xe0e0e0) + open val mediumContrastTextColor = Color(0x9E9E9E) + open val statusBarBackgroundColor = Color(0x333333) + open val toolbarBackgroundColor = Color(0xf4f4f4) + open val toolbarColor = Color(0xffffff) - val lowContrastTextColor = Color(0xe0e0e0) - val mediumContrastTextColor = Color(0x9E9E9E) - val highContrastTextColor = Color(0x202020) - - val cardBackgroundColor = Color(0xFFFFFF) - val appBackgroundColor = Color(0xf4f4f4) - val toolbarBackgroundColor = Color(0xf4f4f4) - val statusBarBackgroundColor = Color(0x333333) - - val headerBackgroundColor = Color(0xeeeeee) - val headerBorderColor = Color(0xcccccc) - val headerTextColor = mediumContrastTextColor - - val itemBackgroundColor = Color(0xffffff) - - fun color(paletteIndex: Int): Color { + open fun color(paletteIndex: Int): Color { return when (paletteIndex) { 0 -> Color(0xD32F2F) - 1 -> Color(0x512DA8) + 1 -> Color(0xE64A19) 2 -> Color(0xF57C00) 3 -> Color(0xFF8F00) 4 -> Color(0xF9A825) @@ -58,6 +54,9 @@ abstract class Theme { 14 -> Color(0x8E24AA) 15 -> Color(0xD81B60) 16 -> Color(0x5D4037) + 17 -> Color(0x424242) + 18 -> Color(0x757575) + 19 -> Color(0x9E9E9E) else -> Color(0x000000) } } @@ -68,3 +67,44 @@ abstract class Theme { } class LightTheme : Theme() + +class DarkTheme : Theme() { + override val appBackgroundColor = Color(0x212121) + override val cardBackgroundColor = Color(0x303030) + override val headerBackgroundColor = Color(0x212121) + override val headerBorderColor = Color(0xcccccc) + override val headerTextColor = Color(0x9E9E9E) + override val highContrastTextColor = Color(0xF5F5F5) + override val itemBackgroundColor = Color(0xffffff) + override val lowContrastTextColor = Color(0x424242) + override val mediumContrastTextColor = Color(0x9E9E9E) + override val statusBarBackgroundColor = Color(0x333333) + override val toolbarBackgroundColor = Color(0xf4f4f4) + override val toolbarColor = Color(0xffffff) + + override fun color(paletteIndex: Int): Color { + return when (paletteIndex) { + 0 -> Color(0xEF9A9A) + 1 -> Color(0xFFAB91) + 2 -> Color(0xFFCC80) + 3 -> Color(0xFFECB3) + 4 -> Color(0xFFF59D) + 5 -> Color(0xE6EE9C) + 6 -> Color(0xC5E1A5) + 7 -> Color(0x69F0AE) + 8 -> Color(0x80CBC4) + 9 -> Color(0x80DEEA) + 10 -> Color(0x81D4FA) + 11 -> Color(0x64B5F6) + 12 -> Color(0x9FA8DA) + 13 -> Color(0xB39DDB) + 14 -> Color(0xCE93D8) + 15 -> Color(0xF48FB1) + 16 -> Color(0xBCAAA4) + 17 -> Color(0xF5F5F5) + 18 -> Color(0xE0E0E0) + 19 -> Color(0x9E9E9E) + else -> Color(0xFFFFFF) + } + } +} diff --git a/android/uhabits-core/src/test/java/org/isoron/platform/gui/JavaCanvasTest.kt b/android/uhabits-core/src/test/java/org/isoron/platform/gui/JavaCanvasTest.kt index a7eb11e69..6f2964233 100644 --- a/android/uhabits-core/src/test/java/org/isoron/platform/gui/JavaCanvasTest.kt +++ b/android/uhabits-core/src/test/java/org/isoron/platform/gui/JavaCanvasTest.kt @@ -29,7 +29,7 @@ import java.awt.image.BufferedImage.TYPE_INT_ARGB class JavaCanvasTest { @Test fun run() = runBlocking { - assertRenders("components/CanvasTest.png", createCanvas(500, 400).apply { drawTestImage() }) + assertRenders("views/CanvasTest.png", createCanvas(500, 400).apply { drawTestImage() }) } } @@ -69,9 +69,9 @@ suspend fun assertRenders( width: Int, height: Int, expectedPath: String, - component: Component, + view: View, ) { val canvas = createCanvas(width, height) - component.draw(canvas) + view.draw(canvas) assertRenders(expectedPath, canvas) } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt index 94e209070..97ac8a601 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt @@ -17,19 +17,17 @@ * with this program. If not, see . */ -package org.isoron.uhabits.components +package org.isoron.uhabits.core.ui.views import kotlinx.coroutines.runBlocking import org.isoron.platform.gui.assertRenders import org.isoron.platform.time.JavaLocalDateFormatter import org.isoron.platform.time.LocalDate -import org.isoron.uhabits.core.ui.views.BarChart -import org.isoron.uhabits.core.ui.views.LightTheme import org.junit.Test import java.util.Locale class BarChartTest { - val base = "components/BarChart" + val base = "views/BarChart" val today = LocalDate(2015, 1, 25) val fmt = JavaLocalDateFormatter(Locale.US) val theme = LightTheme() @@ -37,11 +35,20 @@ class BarChartTest { val axis = (0..100).map { today.minus(it) } val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0) - @Test - fun testDraw() = runBlocking { + init { component.axis = axis component.series.add(series1) component.colors.add(theme.color(8)) + } + + @Test + fun testDraw() = runBlocking { assertRenders(300, 200, "$base/base.png", component) } + + @Test + fun testDrawWithOffset() = runBlocking { + component.dataOffset = 5 + assertRenders(300, 200, "$base/offset.png", component) + } }