mirror of
https://github.com/YTVanced/VancedMicroG
synced 2024-12-03 08:17:26 +00:00
EN-UI: Add popup to ID chart
This commit is contained in:
parent
2f29b93a99
commit
2fe12d8788
3 changed files with 116 additions and 14 deletions
|
@ -9,15 +9,18 @@ import android.annotation.SuppressLint
|
|||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
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
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
class DotChartView : View {
|
||||
|
@ -63,10 +66,12 @@ class DotChartView : View {
|
|||
displayData[14]?.second?.set(hour, displayData[14]?.second?.get(hour) ?: -1)
|
||||
}
|
||||
this.displayData = displayData
|
||||
this.displayDataList = displayData.values.map { it.second.values }.flatten().sorted()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private var displayData: Map<Int, Pair<String, Map<Int, Int>>> = emptyMap()
|
||||
private var displayDataList: List<Int> = emptyList()
|
||||
private val drawPaint = Paint()
|
||||
private val drawTempRect = RectF()
|
||||
private val fontPaint = Paint()
|
||||
|
@ -89,6 +94,9 @@ class DotChartView : View {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T> List<T>.relativePosition(element: T): Double? = indexOf(element).takeIf { it >= 0 }?.toDouble()?.let { it / size.toDouble() }
|
||||
|
||||
private var focusPoint: PointF? = null
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (data == null) data = emptySet()
|
||||
val d = resources.displayMetrics.scaledDensity
|
||||
|
@ -114,20 +122,21 @@ class DotChartView : View {
|
|||
val perHeight = distHeight / 15.0
|
||||
val perWidth = distWidth / 24.0
|
||||
|
||||
val maxValue = displayData.values.mapNotNull { it.second.values.maxOrNull() }.maxOrNull() ?: 0
|
||||
val averageValue = (displayData.values.mapNotNull { it.second.values.average().takeIf { !it.isNaN() } }.average().takeIf { !it.isNaN() }
|
||||
?: 0.0).toInt()
|
||||
val maxValue = displayDataList.last()
|
||||
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
|
||||
var focusDay = -1
|
||||
var focusHour = -1
|
||||
for (day in 0 until 15) {
|
||||
val (dateString, hours) = displayData[day] ?: "" to emptyMap()
|
||||
val top = day * (perHeight + innerPadding) + paddingTop
|
||||
if (day % 2 == 0) {
|
||||
canvas.drawText(dateString, (paddingLeft + legendLeft - 4 * d), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), fontPaint)
|
||||
}
|
||||
focusPoint?.let { if (it.y > top && it.y < top + perHeight) focusDay = day }
|
||||
for (hour in 0 until 24) {
|
||||
val value = hours[hour]
|
||||
val left = hour * (perWidth + innerPadding) + paddingLeft + legendLeft
|
||||
|
@ -139,14 +148,18 @@ class DotChartView : View {
|
|||
canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and 0xffffff)
|
||||
}
|
||||
value >= 0 -> {
|
||||
val alpha = if (value < averageValue) {
|
||||
value.toDouble() / averageValue.toDouble() * 127
|
||||
} else {
|
||||
(value - averageValue).toDouble() / (maxValue - averageValue).toDouble() * 128 + 127
|
||||
}.toInt()
|
||||
val byBucket = ((displayDataList.relativePosition(value) ?: 0.0) * 180.0).toInt()
|
||||
val byMax = (value.toDouble() / maxValue.toDouble() * 80.0).toInt()
|
||||
val alpha = max(0, min(byBucket + byMax, 255))
|
||||
canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and (alpha shl 24 or 0xffffff))
|
||||
}
|
||||
}
|
||||
if (focusDay == day && (value == null || value >= 0)) focusPoint?.let {
|
||||
if (it.x > left && it.x < left + perWidth) {
|
||||
focusHour = hour
|
||||
canvas.drawMyRect(left.toFloat(), top.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and 0xffffff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val legendTop = 15 * (perHeight + innerPadding) + paddingTop + maxTextHeight + 4 * d
|
||||
|
@ -169,10 +182,99 @@ class DotChartView : View {
|
|||
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)
|
||||
canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 2 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and (80 shl 24 or 0xffffff))
|
||||
canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 3 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor and (170 shl 24 or 0xffffff))
|
||||
canvas.drawMyRect((subLeft + (perWidth + innerPadding) * 4 + 12 * d + fontTempRect.width()).toFloat(), subTop.toFloat(), perWidth.toFloat(), perHeight.toFloat(), accentColor)
|
||||
|
||||
val strRecords = context.getString(R.string.pref_exposure_rpis_histogram_legend_records, 0, averageValue, maxValue)
|
||||
canvas.drawText(strRecords, (subLeft + (perWidth + innerPadding) * 3 + 16 * d + fontTempRect.width() + perWidth).toFloat(), (subTop + perHeight / 2.0 + fontTempRect.height() / 2.0).toFloat(), fontPaint)
|
||||
val strRecords = context.getString(R.string.pref_exposure_rpis_histogram_legend_records, "0 - $maxValue")
|
||||
canvas.drawText(strRecords, (subLeft + (perWidth + innerPadding) * 4 + 16 * d + fontTempRect.width() + perWidth).toFloat(), (subTop + perHeight / 2.0 + fontTempRect.height() / 2.0).toFloat(), fontPaint)
|
||||
|
||||
if (focusHour != -1 && Build.VERSION.SDK_INT >= 23) {
|
||||
val floatingColor = context.resolveColor(androidx.appcompat.R.attr.colorBackgroundFloating) ?: 0
|
||||
val line1 = "${displayData[focusDay]?.first}, $focusHour:00"
|
||||
val line2 = displayData[focusDay]?.second?.get(focusHour)?.let { context.getString(R.string.pref_exposure_rpis_histogram_legend_records, it.toString()) }
|
||||
?: strNoRecords
|
||||
fontPaint.textSize = 14 * d
|
||||
fontPaint.isFakeBoldText = true
|
||||
fontPaint.getTextBounds(line1, 0, line1.length, fontTempRect)
|
||||
var fontWidth = fontTempRect.width()
|
||||
val line1Height = fontTempRect.height() + innerPadding
|
||||
fontPaint.isFakeBoldText = false
|
||||
fontPaint.getTextBounds(line2, 0, line2.length, fontTempRect)
|
||||
fontWidth = max(fontWidth, fontTempRect.width())
|
||||
val totalHeight = line1Height + innerPadding + fontTempRect.height()
|
||||
drawPaint.color = floatingColor
|
||||
drawPaint.style = Paint.Style.FILL_AND_STROKE
|
||||
val refTop = focusDay * (perHeight + innerPadding) + paddingTop + perHeight / 2
|
||||
val refLeft = focusHour * (perWidth + innerPadding) + paddingLeft + legendLeft
|
||||
if (refLeft - fontWidth < 50 * d) {
|
||||
// To the right
|
||||
drawTempRect.set((refLeft + perWidth + innerPadding).toFloat(), (refTop - innerPadding - 5 * d - totalHeight / 2f).toFloat(), (refLeft + perWidth + innerPadding + 10 * d + fontWidth).toFloat(), (refTop + innerPadding + 5 * d + totalHeight / 2f).toFloat())
|
||||
} else {
|
||||
// To the left
|
||||
drawTempRect.set((refLeft - innerPadding - 10 * d - fontWidth).toFloat(), (refTop - innerPadding - 5 * d - totalHeight / 2f).toFloat(), (refLeft - innerPadding).toFloat(), (refTop + innerPadding + 5 * d + totalHeight / 2f).toFloat())
|
||||
}
|
||||
canvas.drawRoundRect(drawTempRect, drawPaint.strokeWidth, drawPaint.strokeWidth, drawPaint)
|
||||
val path = Path()
|
||||
if (refLeft - fontWidth < 50 * d) {
|
||||
// To the right
|
||||
val off = refLeft + perWidth + innerPadding + drawPaint.strokeWidth
|
||||
val corr = (perWidth / 2 - innerPadding + drawPaint.strokeWidth)
|
||||
path.moveTo(off.toFloat(), (refTop - corr).toFloat())
|
||||
path.lineTo((refLeft + perWidth / 2).toFloat(), refTop.toFloat())
|
||||
path.lineTo(off.toFloat(), (refTop + corr).toFloat())
|
||||
} else {
|
||||
// To the left
|
||||
val off = refLeft - innerPadding - drawPaint.strokeWidth
|
||||
val corr = (perWidth / 2 - innerPadding + drawPaint.strokeWidth)
|
||||
path.moveTo(off.toFloat(), (refTop - corr).toFloat())
|
||||
path.lineTo((refLeft + perWidth / 2).toFloat(), refTop.toFloat())
|
||||
path.lineTo(off.toFloat(), (refTop + corr).toFloat())
|
||||
}
|
||||
drawPaint.style = Paint.Style.STROKE
|
||||
drawPaint.color = accentColor and (130 shl 24 or 0xffffff)
|
||||
canvas.drawRoundRect(drawTempRect, drawPaint.strokeWidth, drawPaint.strokeWidth, drawPaint)
|
||||
|
||||
drawPaint.color = floatingColor
|
||||
drawPaint.style = Paint.Style.FILL_AND_STROKE
|
||||
path.fillType = Path.FillType.EVEN_ODD
|
||||
canvas.drawPath(path, drawPaint)
|
||||
drawPaint.style = Paint.Style.STROKE
|
||||
drawPaint.color = accentColor and (130 shl 24 or 0xffffff)
|
||||
path.fillType = Path.FillType.EVEN_ODD
|
||||
canvas.drawPath(path, drawPaint)
|
||||
|
||||
canvas.drawText(line2, drawTempRect.left + 5 * d, drawTempRect.top + 5 * d + totalHeight, fontPaint)
|
||||
fontPaint.isFakeBoldText = true
|
||||
canvas.drawText(line1, drawTempRect.left + 5 * d, drawTempRect.top + 5 * d + line1Height, fontPaint)
|
||||
fontPaint.isFakeBoldText = false
|
||||
}
|
||||
}
|
||||
|
||||
private val removeFocusPoint = Runnable { unfocus() }
|
||||
|
||||
fun unfocus() {
|
||||
focusPoint = null
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
postDelayed(removeFocusPoint, POPUP_DELAY)
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_MOVE -> {
|
||||
removeCallbacks(removeFocusPoint)
|
||||
focusPoint = PointF(event.x, event.y)
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val POPUP_DELAY = 3000L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<string name="pref_exposure_app_api_usage_summary_line"><xliff:g example="12">%1$d</xliff:g> Aufrufe von <xliff:g example="provideDiagnosisKeys">%2$s</xliff:g></string>
|
||||
<string name="prefcat_exposure_rpis_histogram_title"><xliff:g example="230">%1$d</xliff:g> gesammelte IDs</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_no_records">Keine Daten</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_records"><xliff:g example="0">%1$d</xliff:g> / <xliff:g example="5">%2$d</xliff:g> / <xliff:g example="50">%3$d</xliff:g> IDs pro Stunde</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_records"><xliff:g example="0 - 50">%1$s</xliff:g> IDs pro Stunde</string>
|
||||
<string name="pref_exposure_rpi_delete_all_title">Löschen</string>
|
||||
<string name="pref_exposure_rpi_delete_all_summary">Alle gesammelten IDs löschen</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning">Nach dem Löschen der gesammelten IDs kannst du nicht mehr informiert werden, falls einer deiner Kontakte der letzten 14 Tage positiv getested wurde.</string>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<string name="pref_exposure_app_api_usage_summary_line"><xliff:g example="12">%1$d</xliff:g> calls to <xliff:g example="provideDiagnosisKeys">%2$s</xliff:g></string>
|
||||
<string name="prefcat_exposure_rpis_histogram_title"><xliff:g example="230">%1$d</xliff:g> IDs collected</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_no_records">No records</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_records"><xliff:g example="0">%1$d</xliff:g> / <xliff:g example="5">%2$d</xliff:g> / <xliff:g example="50">%3$d</xliff:g> IDs per hour</string>
|
||||
<string name="pref_exposure_rpis_histogram_legend_records"><xliff:g example="0 - 50">%1$s</xliff:g> IDs per hour</string>
|
||||
<string name="pref_exposure_rpi_delete_all_title">Delete</string>
|
||||
<string name="pref_exposure_rpi_delete_all_summary">Delete all collected IDs</string>
|
||||
<string name="pref_exposure_rpi_delete_all_warning">Deleting collected IDs will make it impossible to notify you in case any of your contacts of the last 14 days is diagnosed.</string>
|
||||
|
|
Loading…
Reference in a new issue