Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 65032fbb authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

EN-UI: Fix chart in dark mode, add legend

parent 11a86d91
Loading
Loading
Loading
Loading
+77 −56
Original line number Diff line number Diff line
@@ -8,15 +8,13 @@ package org.microg.gms.nearby.core.ui
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.content.res.TypedArray
import android.graphics.*
import android.provider.Settings
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import org.microg.gms.nearby.exposurenotification.ExposureScanSummary
import org.microg.gms.ui.resolveColor
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.max
@@ -69,89 +67,112 @@ class DotChartView : View {
        }

    private var displayData: Map<Int, Pair<String, Map<Int, Int>>> = emptyMap()
    private val paint = Paint()
    private val tempRect = Rect()
    private val tempRectF = RectF()
    private val drawPaint = Paint()
    private val drawTempRect = RectF()
    private val fontPaint = Paint()
    private val fontTempRect = Rect()

    private fun fetchAccentColor(): Int {
        val typedValue = TypedValue()
        val a: TypedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(androidx.appcompat.R.attr.colorAccent))
        val color = a.getColor(0, 0)
        a.recycle()
        return color
    fun Canvas.drawMyRect(x: Float, y: Float, width: Float, height: Float, color: Int) {
        drawTempRect.set(x + drawPaint.strokeWidth, y + drawPaint.strokeWidth, x + width - drawPaint.strokeWidth, y + height - drawPaint.strokeWidth)
        if (Color.alpha(color) >= 80) {
            drawPaint.style = Paint.Style.FILL_AND_STROKE
            drawPaint.color = color
            drawRoundRect(drawTempRect, 2f, 2f, drawPaint)
            drawPaint.style = Paint.Style.FILL
        } else {
            drawPaint.color = color or (80 shl 24) and (80 shl 24 or 0xffffff)
            drawPaint.style = Paint.Style.STROKE
            drawRoundRect(drawTempRect, 2f, 2f, drawPaint)
            drawPaint.style = Paint.Style.FILL
            drawPaint.color = color
            drawRoundRect(drawTempRect, 2f, 2f, drawPaint)
        }
    }

    override fun onDraw(canvas: Canvas) {
        if (data == null) data = emptySet()
        paint.textSize = 10 * resources.displayMetrics.scaledDensity
        paint.isAntiAlias = true
        paint.strokeWidth = 2f
        val d = resources.displayMetrics.scaledDensity
        fontPaint.textSize = 10 * d
        fontPaint.isAntiAlias = true
        drawPaint.isAntiAlias = true
        drawPaint.strokeWidth = 2f
        val innerPadding = 2 * d
        var maxTextWidth = 0
        var maxTextHeight = 0
        for (dateString in displayData.values.map { it.first }) {
            paint.getTextBounds(dateString, 0, dateString.length, tempRect)
            maxTextWidth = max(maxTextWidth, tempRect.width())
            maxTextHeight = max(maxTextHeight, tempRect.height())
            fontPaint.getTextBounds(dateString, 0, dateString.length, fontTempRect)
            maxTextWidth = max(maxTextWidth, fontTempRect.width())
            maxTextHeight = max(maxTextHeight, fontTempRect.height())
        }

        val legendLeft = maxTextWidth + 4 * resources.displayMetrics.scaledDensity
        val legendBottom = maxTextHeight + 4 * resources.displayMetrics.scaledDensity
        val legendLeft = maxTextWidth + 4 * d
        val legendBottom = maxTextHeight + 4 * d
        val subHeight = maxTextHeight + 4 * d + paddingBottom

        val distHeight = (height - 28 - paddingTop - paddingBottom - legendBottom).toDouble()
        val distWidth = (width - 46 - paddingLeft - paddingRight - legendLeft).toDouble()
        val distHeight = (height - innerPadding * 14 - paddingTop - paddingBottom - legendBottom - subHeight).toDouble()
        val distWidth = (width - innerPadding * 23 - paddingLeft - paddingRight - legendLeft).toDouble()
        val perHeight = distHeight / 15.0
        val perWidth = distWidth / 24.0

        paint.textAlign = Paint.Align.RIGHT
        val maxValue = displayData.values.mapNotNull { it.second.values.maxOrNull() }.maxOrNull() ?: 0
        val accentColor = fetchAccentColor()
        val accentRed = Color.red(accentColor)
        val accentGreen = Color.green(accentColor)
        val accentBlue = Color.blue(accentColor)
        val averageValue = (displayData.values.mapNotNull { it.second.values.average().takeIf { !it.isNaN() } }.average().takeIf { !it.isNaN() }
                ?: 0.0).toInt()
        val accentColor = context.resolveColor(androidx.appcompat.R.attr.colorAccent) ?: 0
        val fontColor = context.resolveColor(android.R.attr.textColorSecondary) ?: 0
        val grayBoxColor = fontColor or (255 shl 24) and (80 shl 24 or 0xffffff)
        fontPaint.textAlign = Paint.Align.RIGHT
        fontPaint.color = fontColor
        for (day in 0 until 15) {
            val (dateString, hours) = displayData[day] ?: "" to emptyMap()
            val top = day * (perHeight + 2) + paddingTop
            val top = day * (perHeight + innerPadding) + paddingTop
            if (day % 2 == 0) {
                paint.setARGB(255, 100, 100, 100)
                canvas.drawText(dateString, (paddingLeft + legendLeft - 4 * resources.displayMetrics.scaledDensity), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), paint)
                canvas.drawText(dateString, (paddingLeft + legendLeft - 4 * d), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), fontPaint)
            }
            for (hour in 0 until 24) {
                val value = hours[hour] ?: 0 // TODO: Actually allow null to display offline state as soon as we properly record it
                val left = hour * (perWidth + 2) + paddingLeft + legendLeft
                tempRectF.set(left.toFloat() + 2f, top.toFloat() + 2f, (left + perWidth).toFloat() - 2f, (top + perHeight).toFloat() - 2f)
                val value = hours[hour]
                val left = hour * (perWidth + innerPadding) + paddingLeft + legendLeft
                when {
                    value == null -> {
                        paint.style = Paint.Style.FILL_AND_STROKE
                        paint.setARGB(30, 100, 100, 100)
                        canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
                        paint.style = Paint.Style.FILL
                        canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), grayBoxColor)
                    }
                    maxValue == 0 -> {
                        paint.setARGB(50, accentRed, accentGreen, accentBlue)
                        paint.style = Paint.Style.STROKE
                        canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
                        paint.style = Paint.Style.FILL
                        canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and 0xffffff)
                    }
                    value >= 0 -> {
                        val alpha = ((value.toDouble() / maxValue.toDouble()) * 255).toInt()
                        paint.setARGB(max(50, alpha), accentRed, accentGreen, accentBlue)
                        paint.style = Paint.Style.STROKE
                        canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
                        paint.style = Paint.Style.FILL
                        paint.setARGB(alpha, accentRed, accentGreen, accentBlue)
                        canvas.drawRoundRect(tempRectF, 2f, 2f, paint)
                        val alpha = if (value < averageValue) {
                            value.toDouble() / averageValue.toDouble() * 127
                        } else {
                            (value - averageValue).toDouble() / (maxValue - averageValue).toDouble() * 128 + 127
                        }.toInt()
                        canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and (alpha shl 24 or 0xffffff))
                    }
                }
            }
        }
        val legendTop = 15 * (perHeight + 2) + paddingTop + maxTextHeight + 4 * resources.displayMetrics.scaledDensity
        paint.textAlign = Paint.Align.CENTER
        paint.setARGB(255, 100, 100, 100)
        val legendTop = 15 * (perHeight + innerPadding) + paddingTop + maxTextHeight + 4 * d
        fontPaint.textAlign = Paint.Align.CENTER
        for (hour in 0 until 24) {
            if (hour % 3 == 0) {
                val left = hour * (perWidth + 2) + paddingLeft + legendLeft + perWidth / 2.0
                canvas.drawText("${hour}:00", left.toFloat(), legendTop.toFloat(), paint)
                val left = hour * (perWidth + innerPadding) + paddingLeft + legendLeft + perWidth / 2.0
                canvas.drawText("${hour}:00", left.toFloat(), legendTop.toFloat(), fontPaint)
            }
        }

        val subTop = legendTop + paddingBottom
        val subLeft = paddingLeft + legendLeft

        canvas.drawMyRect(subLeft, subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), grayBoxColor)

        val strNoRecords = "No records"
        fontPaint.textAlign = Paint.Align.LEFT
        fontPaint.getTextBounds(strNoRecords, 0, strNoRecords.length, fontTempRect)
        canvas.drawText(strNoRecords, (subLeft + perWidth + 4 * d).toFloat(), (subTop + perHeight / 2.0 + fontTempRect.height() / 2.0).toFloat(), fontPaint)

        canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 1 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and 0xffffff)
        canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 2 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and (128 shl 24 or 0xffffff))
        canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 3 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor)

        val strRecords = "0 / $averageValue / $maxValue IDs per hour"
        canvas.drawText(strRecords, (subLeft + (perWidth + innerPadding) * 3 + 16 * d + fontTempRect.width() + perWidth).toFloat(), (subTop + perHeight / 2.0 + fontTempRect.height() / 2.0).toFloat(), fontPaint)
    }
}