Skip to content

Commit 7cd7fe7

Browse files
committed
More Views added
• Healthcheck page TODO: • Lockscreen function • notification implementation • Error Dialogs (429, 10s Cooldown, 401, 500, …)
1 parent 36b3560 commit 7cd7fe7

24 files changed

Lines changed: 1297 additions & 13 deletions

.idea/deploymentTargetSelector.xml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId = "de.rpicloud.ipv64net"
1313
minSdk = 28
1414
targetSdk = 36
15-
versionCode = 1
16-
versionName = "1.0"
15+
versionCode = 20
16+
versionName = "2.0"
1717

1818
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1919
}

app/src/main/java/de/rpicloud/ipv64net/helper/NetworkService.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import de.rpicloud.ipv64net.models.IPUpdateResult
1010
import de.rpicloud.ipv64net.models.Logs
1111
import de.rpicloud.ipv64net.models.NetworkResult
1212
import de.rpicloud.ipv64net.models.parseDomainResult
13+
import de.rpicloud.ipv64net.models.parseHealthCheckResult
1314
import de.rpicloud.ipv64net.models.parseIntegrations
1415
import kotlinx.coroutines.Dispatchers
1516
import kotlinx.coroutines.withContext
@@ -457,8 +458,8 @@ class NetworkService {
457458
val response = OkHttpClientProvider.client.newCall(request).execute()
458459
val responseText = response.body.string()
459460
if (response.isSuccessful) {
460-
val result = parseDomainResult(responseText)
461-
println("♻️ - ${result.add_domain}")
461+
val result = parseHealthCheckResult(responseText)
462+
println("♻️ - ${result.get_account_info}")
462463
callback(NetworkResult("Success", result, 200))
463464
} else {
464465
callback(NetworkResult("Fehler: ${response.code}", null, response.code))

app/src/main/java/de/rpicloud/ipv64net/main/activity/MainActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import de.rpicloud.ipv64net.main.views.DomainDetailView
2424
import de.rpicloud.ipv64net.main.views.DomainDnsNewView
2525
import de.rpicloud.ipv64net.main.views.DomainNewView
2626
import de.rpicloud.ipv64net.main.views.DomainView
27+
import de.rpicloud.ipv64net.main.views.HealthcheckDetailView
2728
import de.rpicloud.ipv64net.main.views.HealthcheckView
2829
import de.rpicloud.ipv64net.main.views.IntegrationsView
2930
import de.rpicloud.ipv64net.main.views.LogView
@@ -53,6 +54,9 @@ class MainActivity : ComponentActivity() {
5354
composable(Tabs.Companion.getRoute(Tab.healthcheck)) {
5455
HealthcheckView(navController, mainPadding = mainPadding)
5556
}
57+
composable(Tabs.Companion.getRoute(Tab.healthcheck_details)) {
58+
HealthcheckDetailView(navController, mainPadding = mainPadding)
59+
}
5660
composable(Tabs.Companion.getRoute(Tab.integrations)) {
5761
IntegrationsView(navController, mainPadding = mainPadding)
5862
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package de.rpicloud.ipv64net.main.views
2+
3+
import android.content.Context
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.material3.Card
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.platform.LocalContext
16+
import androidx.compose.ui.text.font.FontWeight
17+
import androidx.compose.ui.tooling.preview.Preview
18+
import androidx.compose.ui.unit.dp
19+
import androidx.core.content.ContextCompat
20+
import de.rpicloud.ipv64net.helper.parseDbDateTime
21+
import de.rpicloud.ipv64net.models.HealthEvents
22+
import de.rpicloud.ipv64net.models.MyLogs
23+
import de.rpicloud.ipv64net.ui.theme.AppTheme
24+
25+
@Composable
26+
fun EventItemView(
27+
ctx: Context,
28+
event: HealthEvents
29+
) {
30+
Card(
31+
modifier = Modifier.fillMaxWidth()
32+
) {
33+
Column(
34+
modifier = Modifier
35+
.padding(16.dp)
36+
.fillMaxWidth(),
37+
horizontalAlignment = Alignment.Start,
38+
verticalArrangement = Arrangement.Center
39+
) {
40+
Text(
41+
event.text,
42+
style = MaterialTheme.typography.titleSmall,
43+
color = Color(ContextCompat.getColor(ctx, event.Status.color))
44+
)
45+
Text(
46+
event.event_time.parseDbDateTime(),
47+
style = MaterialTheme.typography.labelSmall,
48+
modifier = Modifier.padding(top = 5.dp),
49+
color = Color.Gray
50+
)
51+
}
52+
}
53+
}
54+
55+
56+
@Preview(showBackground = true, device = "id:pixel_5")
57+
@Composable
58+
fun EventItemViewPreview() {
59+
AppTheme {
60+
val ctx = LocalContext.current
61+
val event = HealthEvents.empty
62+
event.event_time = "2025-09-25 14:00:00"
63+
event.status = 1
64+
event.text = "Alles Paletti oder was"
65+
EventItemView(ctx, event)
66+
}
67+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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

Comments
 (0)