diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index 8c79fbe87..39a1bd244 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -1433,7 +1433,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1444,7 +1444,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
@@ -2027,7 +2027,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2038,7 +2038,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
@@ -4788,50 +4788,6 @@
column="2"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -4850,7 +4806,7 @@
errorLine2=" ~~~~~~~~">
@@ -4861,7 +4817,7 @@
errorLine2=" ~~~~~~~~">
@@ -4872,7 +4828,7 @@
errorLine2=" ~~~~~~~~">
@@ -4883,7 +4839,7 @@
errorLine2=" ~~~~~~~~">
@@ -4894,7 +4850,7 @@
errorLine2=" ~~~~~~~~">
@@ -4986,50 +4942,6 @@
column="9"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
= if (isInEditMode) {
- listOf(
- 30,
- 60,
- 70,
- 80,
- 130,
- 190,
- 80,
- )
+ listOf(30, 60, 70, 80, 130, 190, 80)
} else {
- listOf(
- 1,
- 1,
- 1,
- 1,
- 1,
- 1,
- 1,
- )
+ listOf(1, 1, 1, 1, 1, 1, 1)
}
set(value) {
field = value.map { max(1, it) }
@@ -96,25 +88,9 @@ class GraphView @JvmOverloads constructor(
}
var secondaryLineData: List = if (isInEditMode) {
- listOf(
- 10,
- 20,
- 40,
- 60,
- 100,
- 132,
- 20,
- )
+ listOf(10, 20, 40, 60, 100, 132, 20)
} else {
- listOf(
- 1,
- 1,
- 1,
- 1,
- 1,
- 1,
- 1,
- )
+ listOf(1, 1, 1, 1, 1, 1, 1)
}
set(value) {
field = value.map { max(1, it) }
@@ -123,48 +99,41 @@ class GraphView @JvmOverloads constructor(
}
init {
- initFromXML(attrs)
- }
-
- private fun initFromXML(attr: AttributeSet?) {
- context.obtainStyledAttributes(attr, R.styleable.GraphView).use { a ->
- primaryLineColor = context.getColor(
- a.getResourceId(
- R.styleable.GraphView_primaryLineColor,
- R.color.tusky_blue,
- ),
+ context.withStyledAttributes(attrs, R.styleable.GraphView, defStyleAttr, R.style.Pachli_Widget_GraphView) {
+ primaryLineColor = getColor(
+ R.styleable.GraphView_primaryLineColor,
+ MaterialColors.getColor(this@GraphView, com.google.android.material.R.attr.colorPrimary),
)
- secondaryLineColor = context.getColor(
- a.getResourceId(
- R.styleable.GraphView_secondaryLineColor,
- R.color.tusky_red,
- ),
+ secondaryLineColor = getColor(
+ R.styleable.GraphView_secondaryLineColor,
+ MaterialColors.getColor(this@GraphView, com.google.android.material.R.attr.colorSecondary),
)
- lineWidth = a.getDimensionPixelSize(
+ metaColor = getColor(
+ R.styleable.GraphView_metaColor,
+ MaterialColors.getColor(this@GraphView, com.google.android.material.R.attr.colorOutline),
+ )
+
+ lineWidth = getDimensionPixelSize(
R.styleable.GraphView_lineWidth,
R.dimen.graph_line_thickness,
).toFloat()
- graphColor = context.getColor(
- a.getResourceId(
- R.styleable.GraphView_graphColor,
- android.R.attr.colorBackground,
- ),
+ graphColor = getColor(
+ R.styleable.GraphView_graphColor,
+ MaterialColors.getColor(this@GraphView, android.R.attr.colorBackground),
)
- metaColor = context.getColor(
- a.getResourceId(
- R.styleable.GraphView_metaColor,
- com.google.android.material.R.attr.dividerColor,
- ),
- )
-
- proportionalTrending = a.getBoolean(
+ proportionalTrending = getBoolean(
R.styleable.GraphView_proportionalTrending,
proportionalTrending,
)
+
+ labelTextSize = getDimensionPixelSize(
+ R.styleable.GraphView_labelTextSize,
+ labelTextSize.toInt(),
+ ).toFloat()
}
primaryLinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
@@ -189,6 +158,20 @@ class GraphView @JvmOverloads constructor(
style = Paint.Style.FILL
}
+ primaryTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = primaryLineColor
+ style = Paint.Style.FILL
+ textSize = labelTextSize
+ textAlign = Paint.Align.RIGHT
+ }
+
+ secondaryTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = secondaryLineColor
+ style = Paint.Style.FILL
+ textSize = labelTextSize
+ textAlign = Paint.Align.RIGHT
+ }
+
graphPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = graphColor
}
@@ -198,6 +181,14 @@ class GraphView @JvmOverloads constructor(
strokeWidth = 0f
style = Paint.Style.STROKE
}
+
+ // Determine how much padding to leave on the right/end of the chart so there's
+ // space for the labels. The widest possible label string is "1000.0M", so
+ // compute that width, with some additional space on the left to separate the
+ // label from the line.
+ val labelBounds = Rect()
+ primaryTextPaint.getTextBounds("1000.0M", 0, 7, labelBounds)
+ paddingEnd = (4 * lineWidth) + labelBounds.width() + labelBounds.left
}
private fun initializeVertices() {
@@ -264,7 +255,7 @@ class GraphView @JvmOverloads constructor(
}
}
- private fun dataSpacing(data: List) = width.toFloat() / max(data.size - 1, 1).toFloat()
+ private fun dataSpacing(data: List) = (width.toFloat() - paddingEnd) / max(data.size - 1, 1).toFloat()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
@@ -279,7 +270,7 @@ class GraphView @JvmOverloads constructor(
val pointDistance = dataSpacing(primaryLineData)
// Vertical tick marks
- for (i in 0 until primaryLineData.size + 1) {
+ for (i in primaryLineData.indices) {
drawLine(
i * pointDistance,
height.toFloat(),
@@ -290,7 +281,7 @@ class GraphView @JvmOverloads constructor(
}
// X-axis
- drawLine(0f, height.toFloat(), width.toFloat(), height.toFloat(), metaPaint)
+ drawLine(0f, height.toFloat(), width.toFloat() - paddingEnd, height.toFloat(), metaPaint)
// Data lines
drawLine(
@@ -298,36 +289,84 @@ class GraphView @JvmOverloads constructor(
linePath = secondaryLinePath,
linePaint = secondaryLinePaint,
circlePaint = secondaryCirclePaint,
- lineThickness = lineWidth,
)
drawLine(
canvas = canvas,
linePath = primaryLinePath,
linePaint = primaryLinePaint,
circlePaint = primaryCirclePaint,
- lineThickness = lineWidth,
+ )
+
+ // Data text
+ drawEndText(
+ canvas,
+ formatNumber(primaryLineData.last(), 1000),
+ formatNumber(secondaryLineData.last(), 1000),
+ primaryLinePath,
+ secondaryLinePath,
)
}
}
- private fun drawLine(
- canvas: Canvas,
- linePath: Path,
- linePaint: Paint,
- circlePaint: Paint,
- lineThickness: Float,
- ) {
+ private fun drawLine(canvas: Canvas, linePath: Path, linePaint: Paint, circlePaint: Paint) {
canvas.apply {
- drawPath(
- linePath,
- linePaint,
- )
-
- val pm = PathMeasure(linePath, false)
- val coord = floatArrayOf(0f, 0f)
- pm.getPosTan(pm.length * 1f, coord, null)
-
- drawCircle(coord[0], coord[1], lineThickness * 2f, circlePaint)
+ drawPath(linePath, linePaint)
+ val (x, y) = pathEnd(linePath)
+ drawCircle(x, y, lineWidth * 2f, circlePaint)
}
}
+
+ private fun drawEndText(
+ canvas: Canvas,
+ primaryValue: String,
+ secondaryValue: String,
+ primaryLinePath: Path,
+ secondaryLinePath: Path,
+ ) {
+ var (primaryX, primaryY) = pathEnd(primaryLinePath)
+ var (_, secondaryY) = pathEnd(secondaryLinePath)
+
+ val primaryBounds = Rect()
+ val secondaryBounds = Rect()
+ primaryTextPaint.getTextBounds(primaryValue, 0, primaryValue.length, primaryBounds)
+ secondaryTextPaint.getTextBounds(secondaryValue, 0, secondaryValue.length, secondaryBounds)
+
+ // Adjust both texts to horizontally align with their respective circle endpoints
+ primaryY += primaryBounds.height().toFloat() / 2
+ secondaryY += secondaryBounds.height().toFloat() / 2
+
+ // Force the two apart if they overlap
+ val overlap = primaryY - (secondaryY - secondaryBounds.height())
+ // First try and force them both apart
+ if (overlap > 0) {
+ secondaryY += (overlap / 2) + 5
+ primaryY -= (overlap / 2) + 5
+ }
+ // Now, if secondary is off the canvas move both of them up to compensate
+ val secondaryClip = secondaryY - canvas.height
+ if (secondaryClip > 0) {
+ secondaryY -= secondaryClip
+ primaryY -= secondaryClip
+ }
+
+ // The number text is right aligned to ensure they line up. The primary text
+ // (total usage) is always going to be larger than the secondary text, so use
+ // that to determine the X position of the right-hand edge of the text. This is:
+ // - primaryX
+ // - + 4 * lineWidth (spacing between the line circle and the text)
+ // - + primaryBounds.width() (width of the text)
+ // - + primaryBounds.left (left margin of the text)
+ val textX = primaryX + (4 * lineWidth) + primaryBounds.width() + primaryBounds.left
+ canvas.apply {
+ drawText(primaryValue, textX, primaryY, primaryTextPaint)
+ drawText(secondaryValue, textX, secondaryY, secondaryTextPaint)
+ }
+ }
+
+ private fun pathEnd(path: Path): Pair {
+ val pm = PathMeasure(path, false)
+ val coord = floatArrayOf(0f, 0f)
+ pm.getPosTan(pm.length * 1f, coord, null)
+ return Pair(coord[0], coord[1])
+ }
}
diff --git a/app/src/main/res/layout-land/item_trending_cell.xml b/app/src/main/res/layout-land/item_trending_cell.xml
index 541dfd256..1b380e957 100644
--- a/app/src/main/res/layout-land/item_trending_cell.xml
+++ b/app/src/main/res/layout-land/item_trending_cell.xml
@@ -21,48 +21,10 @@
android:importantForAccessibility="no"
app:graphColor="?android:colorBackground"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/current_usage"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:lineWidth="2sp"
- app:metaColor="@color/tusky_blue_light"
- app:primaryLineColor="@color/tusky_blue"
- app:proportionalTrending="true"
- app:secondaryLineColor="@color/tusky_red" />
-
-
-
-
+ app:proportionalTrending="true" />
-
diff --git a/app/src/main/res/layout/item_trending_cell.xml b/app/src/main/res/layout/item_trending_cell.xml
index 97e48bd60..2c48cdba7 100644
--- a/app/src/main/res/layout/item_trending_cell.xml
+++ b/app/src/main/res/layout/item_trending_cell.xml
@@ -21,48 +21,10 @@
android:importantForAccessibility="no"
app:graphColor="?android:colorBackground"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/current_usage"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:lineWidth="2sp"
- app:metaColor="@color/tusky_blue_light"
- app:primaryLineColor="@color/tusky_blue"
- app:proportionalTrending="true"
- app:secondaryLineColor="@color/tusky_red" />
-
-
-
-
+ app:proportionalTrending="true" />
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/attrs_graphview.xml b/app/src/main/res/values/attrs_graphview.xml
new file mode 100644
index 000000000..9b9082135
--- /dev/null
+++ b/app/src/main/res/values/attrs_graphview.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 0f0579888..3731f7020 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -65,12 +65,16 @@
#2b90d9
- #56a7e1
#ca8f04
#fab207
#19a341
#25d069
- #DF1553
+
+ #006382
+ #F37B2F
+
+
+
#fff
#000
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 9d5628fa0..5b7b48717 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -80,6 +80,8 @@
- @android:color/transparent
- ?attr/isLightTheme
+
+ - @style/Pachli.Widget.GraphView
@@ -159,6 +161,12 @@
- @color/black
+
+