1+ package de.rpicloud.ipv64net.main.views
2+
3+ import android.content.Context
4+ import androidx.compose.foundation.Canvas
5+ import androidx.compose.foundation.background
6+ import androidx.compose.foundation.layout.Arrangement
7+ import androidx.compose.foundation.layout.Box
8+ import androidx.compose.foundation.layout.BoxWithConstraints
9+ import androidx.compose.foundation.layout.PaddingValues
10+ import androidx.compose.foundation.layout.fillMaxWidth
11+ import androidx.compose.foundation.layout.height
12+ import androidx.compose.foundation.layout.padding
13+ import androidx.compose.foundation.layout.size
14+ import androidx.compose.foundation.layout.width
15+ import androidx.compose.foundation.lazy.LazyRow
16+ import androidx.compose.foundation.lazy.items
17+ import androidx.compose.material3.Card
18+ import androidx.compose.material3.CardDefaults
19+ import androidx.compose.runtime.Composable
20+ import androidx.compose.runtime.derivedStateOf
21+ import androidx.compose.runtime.getValue
22+ import androidx.compose.runtime.remember
23+ import androidx.compose.ui.Modifier
24+ import androidx.compose.ui.geometry.CornerRadius
25+ import androidx.compose.ui.geometry.Offset
26+ import androidx.compose.ui.geometry.Size
27+ import androidx.compose.ui.graphics.Color
28+ import androidx.compose.ui.graphics.RectangleShape
29+ import androidx.compose.ui.platform.LocalDensity
30+ import androidx.compose.ui.unit.Dp
31+ import androidx.compose.ui.unit.LayoutDirection
32+ import androidx.compose.ui.unit.dp
33+ import androidx.core.content.ContextCompat
34+ import de.rpicloud.ipv64net.models.HealthEvents
35+
36+ @Composable
37+ fun EventsBarView (
38+ ctx : Context ,
39+ events : List <HealthEvents >, // immutable Liste bevorzugen
40+ modifier : Modifier = Modifier ,
41+ maxVisible : Int? = null, // null => so viele wie reinpassen
42+ ) {
43+ // Feste Maße einmal definieren
44+ val itemW = 5 .dp
45+ val itemH = 20 .dp
46+ val gap = 5 .dp
47+ val outerPad = PaddingValues (start = 16 .dp, end = 16 .dp, bottom = 16 .dp)
48+
49+ BoxWithConstraints (modifier = modifier.fillMaxWidth()) {
50+ // Wie viele passen rein? (wenn maxVisible gesetzt ist, nimm den kleineren)
51+ val visibleCount = remember(maxWidth, maxVisible) {
52+ if (maxVisible != null ) maxVisible
53+ else {
54+ // floor((W - Außenpadding + gap) / (itemW + gap))
55+ val innerW = maxWidth - (outerPad.calculateLeftPadding(LayoutDirection .Ltr )
56+ + outerPad.calculateRightPadding(LayoutDirection .Ltr ))
57+ ((innerW + gap) / (itemW + gap)).toInt().coerceAtLeast(1 )
58+ }
59+ }
60+
61+ // Slice der Daten nur einmal berechnen, und ohne .reversed()–Allokation
62+ // -> iteriere rückwärts per indices
63+ val displayEvents by remember(events, visibleCount) {
64+ derivedStateOf {
65+ val n = minOf(visibleCount, events.size)
66+ // neueste/letzte zuerst (wie dein reversed())
67+ val out = ArrayList <HealthEvents >(n)
68+ var i = events.lastIndex
69+ repeat(n) {
70+ out .add(events[i-- ])
71+ }
72+ out
73+ }
74+ }
75+
76+ LazyRow (
77+ modifier = Modifier .padding(outerPad),
78+ horizontalArrangement = Arrangement .spacedBy(gap)
79+ ) {
80+ items(
81+ items = displayEvents,
82+ key = { e -> e.id }, // stabile Keys
83+ contentType = { _ -> 0 } // gleicher Typ → effizienteres Recycling
84+ ) { e ->
85+ // ultraleicht: Box + background statt Card
86+ Box (
87+ modifier = Modifier
88+ .size(width = itemW, height = itemH)
89+ .background(Color (ContextCompat .getColor(ctx, e.Status .color)), shape = RectangleShape )
90+ )
91+ }
92+ }
93+ }
94+ }
95+
96+ @Composable
97+ fun EventsBarCanvas (
98+ events : List <HealthEvents >,
99+ modifier : Modifier = Modifier ,
100+ maxVisible : Int? = null, // null => so viele wie reinpassen
101+ barWidth : Dp = 5.dp,
102+ barHeight : Dp = 20.dp,
103+ gap : Dp = 5.dp,
104+ cornerRadius : Dp = 5.dp,
105+ horizontalPadding : Dp = 16.dp,
106+ bottomPadding : Dp = 16.dp,
107+ // Farb-Mapper: falls deine Events schon eine Compose-Farbe haben, reiche sie hier durch
108+ statusToColor : (HealthEvents ) -> Color
109+ ) {
110+ BoxWithConstraints (modifier = modifier.fillMaxWidth()) {
111+ val density = LocalDensity .current
112+ val maxW = this .maxWidth
113+
114+ // wie viele Balken passen rein?
115+ val visibleCount = remember(maxW, maxVisible, barWidth, gap, horizontalPadding) {
116+ if (maxVisible != null ) maxVisible
117+ else {
118+ val innerW = maxW - horizontalPadding * 2
119+ val per = barWidth + gap
120+ ((innerW + gap) / per).toInt().coerceAtLeast(1 )
121+ }
122+ }
123+
124+ val revEvents = events.reversed()
125+ // letzte N Events (neueste zuerst), ohne ständig neue Kopien zu bauen
126+ val displayEvents by remember(revEvents, visibleCount) {
127+ derivedStateOf {
128+ val n = minOf(visibleCount, revEvents.size)
129+ ArrayList <HealthEvents >(n).apply {
130+ var i = revEvents.lastIndex
131+ repeat(n) { add(revEvents[i-- ]) }
132+ }
133+ }
134+ }
135+
136+ val height = barHeight + bottomPadding
137+
138+ Canvas (
139+ modifier = Modifier
140+ .height(height)
141+ .fillMaxWidth()
142+ .padding(start = horizontalPadding, end = horizontalPadding, bottom = bottomPadding)
143+ ) {
144+ val bw = with (density) { barWidth.toPx() }
145+ val g = with (density) { gap.toPx() }
146+ val h = with (density) { barHeight.toPx() }
147+ val r = with (density) { cornerRadius.toPx() }
148+
149+ var x = 0f
150+ displayEvents.reversed().forEach { e ->
151+ val color = statusToColor(e)
152+ drawRoundRect(
153+ cornerRadius = CornerRadius (r, r),
154+ color = color,
155+ topLeft = Offset (x, size.height - h), // unten ausrichten
156+ size = Size (bw, h)
157+ )
158+ x + = bw + g
159+ }
160+ }
161+ }
162+ }
0 commit comments