Skip to content

Commit 712b0b1

Browse files
Merge branch 'feature/ADFA-5509-build-annotations' into feature/ADFA-5527-chart-font-scale
2 parents 38e6878 + 7509593 commit 712b0b1

4 files changed

Lines changed: 120 additions & 3 deletions

File tree

app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,20 @@ abstract class BaseEditorActivity :
471471
* handed to [MetricsCarouselController], which is in turn handed to the floating window and
472472
* outlives an activity recreation. A pure function of the process name has no business
473473
* pinning an activity in memory, and this one is exactly that.
474+
*
475+
* An unrecognised name falls back rather than throwing. This is reached from the
476+
* once-a-second sample listener and from RecyclerView's bind pass, so a name nobody added a
477+
* colour for would take the editor down from a timer callback or mid-layout -- a crash for
478+
* the sake of a line colour. 5d00a796a and 4c65554e5 each established that; this branch
479+
* removed it again, so it is written down here rather than rediscovered a fourth time.
474480
*/
475481
@JvmStatic
476482
fun getMemUsageLineColorFor(proc: MemoryUsageWatcher.ProcessMemoryInfo): Int =
477483
when (proc.pname) {
478484
PROC_IDE -> Color.BLUE
479485
PROC_GRADLE_TOOLING -> Color.RED
480486
PROC_GRADLE_DAEMON -> Color.GREEN
481-
else -> throw IllegalArgumentException("Unknown process: $proc")
487+
else -> Color.GRAY
482488
}
483489

484490
protected val PROC_IDE = "IDE"
@@ -1018,11 +1024,21 @@ abstract class BaseEditorActivity :
10181024
}
10191025

10201026
private fun setupMetricsCarousel() {
1021-
metricsCarousel.bind(binding.memUsageView)
10221027
binding.memUsageView.root.onTwoFingerTap = ::onMetricsCarouselUndockRequested
10231028
binding.memUsageView.metricsUndockedMessage.setOnClickListener {
10241029
onMetricsCarouselRedockRequested()
10251030
}
1031+
1032+
// Ask where the carousel is before binding one here. Only one can be live at a time, and
1033+
// the floating one outlives this activity -- so an activity recreated while it is floating
1034+
// (a night-mode or locale change, or leaving the editor and coming back) used to bind a
1035+
// second carousel into the strip and leave the floating one attached to a destroyed
1036+
// activity's views, frozen, with the strip showing no sign that it had gone anywhere.
1037+
//
1038+
// [setMetricsCarouselUndocked] is the same call the undock request makes, so the strip
1039+
// shows the "tap to bring them back" message and tapping it re-docks onto *this*
1040+
// activity's controller.
1041+
setMetricsCarouselUndocked(isMetricsCarouselUndocked())
10261042
}
10271043

10281044
/**

app/src/main/java/com/itsaky/androidide/ui/MetricsChartRenderer.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,25 @@ abstract class MetricsChartRenderer(
100100
* One predicate, because the tap that opens the sampling-rate chooser and the long press that
101101
* explains it have to agree on where that band is: written twice, they can drift apart and the
102102
* tooltip then describes a control the tap no longer reaches.
103+
*
104+
* Bounded below, not just above. Everything under the plot used to count, and the legend lives
105+
* there too -- MPAndroidChart aligns it to the bottom by default, under the axis labels. So
106+
* tapping the legend, which is the one thing in a chart a reader expects to be tappable, opened
107+
* the sampling-rate chooser; picking a rate there clears every buffer, and the user loses the
108+
* history they were looking at for an action they did not ask for.
109+
*
110+
* The band stops at the legend's top edge, and is never narrower than one axis label, so a
111+
* legend that measures larger than expected cannot squeeze the rate chooser out of reach.
103112
*/
104113
private fun isOnAxisBand(y: Float): Boolean {
105114
val chart = this.chart ?: return false
106-
return y >= chart.viewPortHandler.contentBottom()
115+
val top = chart.viewPortHandler.contentBottom()
116+
val legend = chart.legend
117+
// What the chart reserves for the legend at the bottom: its measured height plus the
118+
// offset it keeps above itself. Both are pixels, as MPAndroidChart stores them.
119+
val reservedForLegend = if (legend.isEnabled) legend.mNeededHeight + legend.yOffset else 0f
120+
val bottom = maxOf(chart.height - reservedForLegend, top + chart.xAxis.textSize)
121+
return y >= top && y < bottom
107122
}
108123

109124
/**
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.activities.editor
19+
20+
import android.graphics.Color
21+
import com.google.common.truth.Truth.assertThat
22+
import com.itsaky.androidide.utils.MemoryUsageWatcher
23+
import com.itsaky.androidide.utils.MutableShiftedLongArray
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.robolectric.RobolectricTestRunner
27+
28+
/**
29+
* That an unnamed process costs a line colour rather than the editor.
30+
*
31+
* This fallback has been established twice and removed twice. It is reached from the once-a-second
32+
* sample listener and from RecyclerView's bind pass, so throwing here takes the editor down from a
33+
* timer callback or mid-layout -- for the sake of a colour.
34+
*/
35+
@RunWith(RobolectricTestRunner::class)
36+
class MemUsageLineColorTest {
37+
private fun process(name: String) =
38+
MemoryUsageWatcher.ProcessMemoryInfo(
39+
pid = 1234,
40+
pname = name,
41+
_history = MutableShiftedLongArray(4),
42+
)
43+
44+
@Test
45+
fun `the three watched processes keep their colours`() {
46+
assertThat(BaseEditorActivity.getMemUsageLineColorFor(process("IDE"))).isEqualTo(Color.BLUE)
47+
assertThat(BaseEditorActivity.getMemUsageLineColorFor(process("Gradle Tooling"))).isEqualTo(Color.RED)
48+
assertThat(BaseEditorActivity.getMemUsageLineColorFor(process("Gradle Daemon"))).isEqualTo(Color.GREEN)
49+
}
50+
51+
@Test
52+
fun `a process nobody gave a colour gets one anyway`() {
53+
// Not a throw. The names are only ever supplied by watchProcess call sites today, so this
54+
// is a guard rather than a live path -- but the cost of being wrong is a crash from a
55+
// timer callback, and the cost of the guard is one grey line.
56+
assertThat(BaseEditorActivity.getMemUsageLineColorFor(process("Kotlin Daemon"))).isEqualTo(Color.GRAY)
57+
}
58+
}

app/src/test/java/com/itsaky/androidide/ui/MetricsChartAxisTapTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,34 @@ class MetricsChartAxisTapTest {
128128
assertThat(taps).isEqualTo(1)
129129
}
130130

131+
@Test
132+
fun `a tap on the legend does not open the chooser`() {
133+
val chart = laidOutChart()
134+
135+
// MPAndroidChart aligns the legend to the bottom by default, below the axis labels, so
136+
// "everything under the plot" included it -- and the legend is the one part of a chart a
137+
// reader expects to be tappable. Opening the rate chooser there is bad enough; picking a
138+
// rate in it clears every buffer, so a mis-tap costs the history being looked at.
139+
//
140+
// Robolectric measures no real text, so the legend here is a few pixels rather than the
141+
// ~10dp row a device draws. That is enough: the assertion is about which side of the
142+
// boundary the legend's own rows fall on, and the bottom row is the legend's.
143+
tapAt(chart, CHART_HEIGHT - 1f)
144+
145+
assertThat(taps).isEqualTo(0)
146+
}
147+
148+
@Test
149+
fun `the axis labels still open the chooser, with the legend excluded`() {
150+
val chart = laidOutChart()
151+
152+
// The other half of the bound: narrowing the band must not put the rate chooser out of
153+
// reach. One axis label's height below the plot always stays in it.
154+
tapAt(chart, chart.viewPortHandler.contentBottom() + chart.xAxis.textSize / 2f)
155+
156+
assertThat(taps).isEqualTo(1)
157+
}
158+
131159
@Test
132160
fun `a tap above the plot does not open the chooser`() {
133161
val chart = laidOutChart()

0 commit comments

Comments
 (0)