Skip to content

Commit ccd99f1

Browse files
committed
home: Display detailed RAM usage with a progress bar
Signed-off-by: Rve27 <rve27github@gmail.com>
1 parent 99d53fd commit ccd99f1

5 files changed

Lines changed: 131 additions & 20 deletions

File tree

composeApp/src/jvmMain/kotlin/com/rve/rvkernelmanager/ui/components/Card.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import androidx.compose.ui.draw.clip
4949
import androidx.compose.ui.graphics.ColorFilter
5050
import androidx.compose.ui.graphics.Shape
5151
import androidx.compose.ui.unit.dp
52+
import androidx.compose.ui.text.style.TextOverflow
5253
import com.rve.rvkernelmanager.ui.data.AppIcon
5354

5455
object Card {
@@ -93,11 +94,15 @@ object Card {
9394
text = title,
9495
style = MaterialTheme.typography.titleMedium,
9596
color = MaterialTheme.colorScheme.onSurface,
97+
maxLines = 1,
98+
overflow = TextOverflow.Ellipsis,
9699
)
97100
Text(
98101
text = summary,
99102
style = MaterialTheme.typography.bodyMedium,
100103
color = MaterialTheme.colorScheme.onSurfaceVariant,
104+
maxLines = 3,
105+
overflow = TextOverflow.Ellipsis,
101106
)
102107
}
103108
}

composeApp/src/jvmMain/kotlin/com/rve/rvkernelmanager/ui/data/DeviceInfo.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ data class DeviceInfo(
3535
val os: String = "RvOS",
3636
val cpu: String = "unknown",
3737
val gpu: String = "unknown",
38-
val ram: String = "unknown",
38+
val ramTotal: String = "unknown",
39+
val ramUsed: String = "unknown",
40+
val ramFree: String = "unknown",
3941
val isZramActive: Boolean = false,
4042
val zram: String = "unknown",
4143
val isSwapActive: Boolean = false,

composeApp/src/jvmMain/kotlin/com/rve/rvkernelmanager/ui/screen/HomeScreen.kt

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,23 @@ import androidx.compose.animation.shrinkOut
3939
import androidx.compose.foundation.background
4040
import androidx.compose.foundation.layout.Arrangement
4141
import androidx.compose.foundation.layout.Box
42+
import androidx.compose.foundation.layout.Column
4243
import androidx.compose.foundation.layout.PaddingValues
44+
import androidx.compose.foundation.layout.Row
4345
import androidx.compose.foundation.layout.fillMaxSize
46+
import androidx.compose.foundation.layout.fillMaxWidth
4447
import androidx.compose.foundation.layout.padding
4548
import androidx.compose.foundation.lazy.grid.GridCells
4649
import androidx.compose.foundation.lazy.grid.GridItemSpan
4750
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
4851
import androidx.compose.foundation.lazy.grid.items
52+
import androidx.compose.foundation.shape.CircleShape
4953
import androidx.compose.foundation.shape.RoundedCornerShape
54+
import androidx.compose.material3.Card
55+
import androidx.compose.material3.CardDefaults
5056
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
57+
import androidx.compose.material3.Icon
58+
import androidx.compose.material3.LinearWavyProgressIndicator
5159
import androidx.compose.material3.MaterialShapes
5260
import androidx.compose.material3.MaterialTheme
5361
import androidx.compose.material3.Scaffold
@@ -56,6 +64,7 @@ import androidx.compose.material3.toShape
5664
import androidx.compose.runtime.Composable
5765
import androidx.compose.runtime.DisposableEffect
5866
import androidx.compose.runtime.getValue
67+
import androidx.compose.ui.Alignment
5968
import androidx.compose.ui.Modifier
6069
import androidx.compose.ui.draw.clip
6170
import androidx.compose.ui.text.font.FontWeight
@@ -143,12 +152,6 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel { HomeViewModel() }) {
143152
title = "GPU",
144153
summary = deviceInfo.gpu,
145154
),
146-
HomeItem(
147-
icon = AppIcon.ImageVectorIcon(MaterialSymbols.RoundedFilled.Memory),
148-
containerIconShape = MaterialShapes.Square.toShape(),
149-
title = "RAM",
150-
summary = deviceInfo.ram,
151-
),
152155
)
153156

154157
Box(
@@ -203,6 +206,14 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel { HomeViewModel() }) {
203206
)
204207
}
205208

209+
item(span = { GridItemSpan(minOf(2, maxLineSpan)) }) {
210+
RamUsageCard(
211+
ramTotal = deviceInfo.ramTotal,
212+
ramUsed = deviceInfo.ramUsed,
213+
ramFree = deviceInfo.ramFree,
214+
)
215+
}
216+
206217
if (deviceInfo.isZramActive || deviceInfo.isSwapActive) {
207218
item(span = { GridItemSpan(maxLineSpan) }) {
208219
Text(
@@ -268,3 +279,60 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel { HomeViewModel() }) {
268279
}
269280
}
270281
}
282+
283+
@Composable
284+
private fun RamUsageCard(ramTotal: String, ramUsed: String, ramFree: String) {
285+
val totalVal = ramTotal.substringBefore(" ").toFloatOrNull() ?: 0f
286+
val usedVal = ramUsed.substringBefore(" ").toFloatOrNull() ?: 0f
287+
val progress = if (totalVal > 0f) usedVal / totalVal else 0f
288+
289+
Card(
290+
colors = CardDefaults.cardColors(
291+
containerColor = MaterialTheme.colorScheme.surfaceBright,
292+
),
293+
shape = MaterialTheme.shapes.extraLarge,
294+
modifier = Modifier.fillMaxWidth()
295+
) {
296+
Column(
297+
modifier = Modifier.padding(16.dp),
298+
verticalArrangement = Arrangement.spacedBy(16.dp)
299+
) {
300+
Row(
301+
verticalAlignment = Alignment.CenterVertically,
302+
horizontalArrangement = Arrangement.spacedBy(16.dp)
303+
) {
304+
Box(
305+
modifier = Modifier
306+
.clip(MaterialShapes.Square.toShape())
307+
.background(MaterialTheme.colorScheme.primary)
308+
.padding(8.dp),
309+
contentAlignment = Alignment.Center,
310+
) {
311+
Icon(
312+
imageVector = MaterialSymbols.RoundedFilled.Memory,
313+
contentDescription = null,
314+
tint = MaterialTheme.colorScheme.onPrimary,
315+
)
316+
}
317+
Column {
318+
Text(
319+
text = "RAM Usage",
320+
style = MaterialTheme.typography.titleMedium,
321+
color = MaterialTheme.colorScheme.onSurface,
322+
)
323+
Text(
324+
text = "$ramUsed / $ramTotal (Free: $ramFree)",
325+
style = MaterialTheme.typography.bodyMedium,
326+
color = MaterialTheme.colorScheme.onSurfaceVariant,
327+
)
328+
}
329+
}
330+
LinearWavyProgressIndicator(
331+
progress = { progress },
332+
modifier = Modifier.fillMaxWidth().clip(CircleShape),
333+
color = MaterialTheme.colorScheme.primary,
334+
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
335+
)
336+
}
337+
}
338+
}

composeApp/src/jvmMain/kotlin/com/rve/rvkernelmanager/ui/viewmodel/HomeViewModel.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ import androidx.lifecycle.ViewModel
3333
import androidx.lifecycle.viewModelScope
3434
import com.rve.rvkernelmanager.ui.data.DeviceInfo
3535
import com.rve.rvkernelmanager.utils.Utils.getCpuModel
36+
import com.rve.rvkernelmanager.utils.Utils.getFreeRam
3637
import com.rve.rvkernelmanager.utils.Utils.getGpuModel
3738
import com.rve.rvkernelmanager.utils.Utils.getHostname
3839
import com.rve.rvkernelmanager.utils.Utils.getKernelVersion
3940
import com.rve.rvkernelmanager.utils.Utils.getOS
40-
import com.rve.rvkernelmanager.utils.Utils.getRamStatus
41+
import com.rve.rvkernelmanager.utils.Utils.getTotalRam
4142
import com.rve.rvkernelmanager.utils.Utils.getTotalSwap
4243
import com.rve.rvkernelmanager.utils.Utils.getTotalZram
44+
import com.rve.rvkernelmanager.utils.Utils.getUsedRam
4345
import com.rve.rvkernelmanager.utils.Utils.getUsername
4446
import com.rve.rvkernelmanager.utils.Utils.isSwapActive
4547
import com.rve.rvkernelmanager.utils.Utils.isZramActive
@@ -75,7 +77,9 @@ class HomeViewModel : ViewModel() {
7577
isSwapActive = isSwapActive(),
7678
swap = getTotalSwap(),
7779
kernel = getKernelVersion(),
78-
ram = getRamStatus(),
80+
ramTotal = getTotalRam(),
81+
ramUsed = getUsedRam(),
82+
ramFree = getFreeRam(),
7983
)
8084
}
8185
}
@@ -86,9 +90,15 @@ class HomeViewModel : ViewModel() {
8690
ramJob = viewModelScope.launch(Dispatchers.IO) {
8791
while (isActive) {
8892
delay(3000)
89-
val currentRam = getRamStatus()
93+
val currentRamTotal = getTotalRam()
94+
val currentRamUsed = getUsedRam()
95+
val currentRamFree = getFreeRam()
9096
_deviceInfo.update { currentState ->
91-
currentState.copy(ram = currentRam)
97+
currentState.copy(
98+
ramTotal = currentRamTotal,
99+
ramUsed = currentRamUsed,
100+
ramFree = currentRamFree,
101+
)
92102
}
93103
}
94104
}

composeApp/src/jvmMain/kotlin/com/rve/rvkernelmanager/utils/Utils.kt

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,39 @@ object Utils {
9797
"unknown"
9898
}
9999

100-
fun getRamStatus(): String = runCatching {
100+
fun getTotalRam(): String = runCatching {
101101
File("/proc/meminfo").useLines { seq ->
102-
val lines = seq.toList()
102+
val totalLine = seq.find { it.startsWith("MemTotal:") }
103+
val totalKb = totalLine?.replace(Regex("\\D+"), "")?.toLong() ?: 0L
104+
if (totalKb > 0) {
105+
val df = DecimalFormat("#.##")
106+
"${df.format(totalKb / (1024.0 * 1024.0))} GB"
107+
} else {
108+
"unknown"
109+
}
110+
}
111+
}.getOrElse {
112+
"unknown"
113+
}
114+
115+
fun getFreeRam(): String = runCatching {
116+
File("/proc/meminfo").useLines { seq ->
117+
val availableLine = seq.find { it.startsWith("MemAvailable:") }
118+
val availableKb = availableLine?.replace(Regex("\\D+"), "")?.toLong() ?: 0L
119+
if (availableKb > 0) {
120+
val df = DecimalFormat("#.##")
121+
"${df.format(availableKb / (1024.0 * 1024.0))} GB"
122+
} else {
123+
"unknown"
124+
}
125+
}
126+
}.getOrElse {
127+
"unknown"
128+
}
103129

130+
fun getUsedRam(): String = runCatching {
131+
File("/proc/meminfo").useLines { seq ->
132+
val lines = seq.toList()
104133
val totalLine = lines.find { it.startsWith("MemTotal:") }
105134
val availableLine = lines.find { it.startsWith("MemAvailable:") }
106135

@@ -109,17 +138,13 @@ object Utils {
109138

110139
if (totalKb > 0) {
111140
val usedKb = totalKb - availableKb
112-
113141
val df = DecimalFormat("#.##")
114-
fun toGb(kb: Long): String = df.format(kb / (1024.0 * 1024.0))
115-
116-
"Total: ${toGb(totalKb)} GB\nUsed: ${toGb(usedKb)} GB\nFree: ${toGb(availableKb)} GB"
142+
"${df.format(usedKb / (1024.0 * 1024.0))} GB"
117143
} else {
118144
"unknown"
119145
}
120146
}
121-
}.getOrElse { e ->
122-
Log.e("Utils", "Failed to get RAM status", e)
147+
}.getOrElse {
123148
"unknown"
124149
}
125150

@@ -191,7 +216,8 @@ object Utils {
191216
}
192217

193218
fun getKernelVersion(): String = runCatching {
194-
File("/proc/version").readText().trim()
219+
val versionInfo = File("/proc/version").readText().trim()
220+
versionInfo.split(" ").getOrNull(2) ?: versionInfo
195221
}.getOrElse { e ->
196222
Log.e("Utils", "Failed to read /proc/version", e)
197223
"unknown"

0 commit comments

Comments
 (0)