[SHARED] Add info about running as admin on temperatures

This commit is contained in:
Kamil Gurgul
2026-05-27 08:10:40 +02:00
parent 8647341aba
commit f60674f2e8
8 changed files with 77 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# Project
This is a compose multiplatform project build for Android, iOS, JVM and web. App is focused around
hardware and software of the devices. App is built using MVVM architecture with AndroidX lifecycle
and Kotlin Coroutines for asynchronous programming. It is using DataStore for key-value storage.
# Commands
- Run tests: `./gradlew allTests`
- Format code: `./gradlew spotlessApply`
# Code style
- Use 4 spaces for indentation
- Pass data to VM with Interactor
- From VM expose data as StateFlow using single UiState
- For events use Channel<Event>(Channel.BUFFERED) and observe them in compose with ObserveAsEvents
- In VM use `on` prefix for callback functions like `onEvent`, `onClick` etc.
- Use composeResources for all strings
@@ -212,6 +212,7 @@
<string name="no_temp_data">Je mi líto, nemohu najít žádné teplotní senzory :-(</string> <string name="no_temp_data">Je mi líto, nemohu najít žádné teplotní senzory :-(</string>
<string name="no_temp_data_admin_required">Čtení teplotních senzorů vyžaduje oprávnění správce. Spusťte aplikaci jako správce pro přístup k teplotním údajům.</string> <string name="no_temp_data_admin_required">Čtení teplotních senzorů vyžaduje oprávnění správce. Spusťte aplikaci jako správce pro přístup k teplotním údajům.</string>
<string name="temp_admin_required_snackbar">Pro načtení teplot prosím spusťte jako správce</string>
<string name="temp_thermal_state">Tepelný stav: %1$s</string> <string name="temp_thermal_state">Tepelný stav: %1$s</string>
<string name="temp_thermal_state_nominal">Nominální</string> <string name="temp_thermal_state_nominal">Nominální</string>
<string name="temp_thermal_state_fair">Dobrý</string> <string name="temp_thermal_state_fair">Dobrý</string>
@@ -212,6 +212,7 @@
<string name="no_temp_data">Entschuldigung, ich kann keine Temperatursensoren finden :-(</string> <string name="no_temp_data">Entschuldigung, ich kann keine Temperatursensoren finden :-(</string>
<string name="no_temp_data_admin_required">Das Lesen von Temperatursensoren erfordert Administratorrechte. Bitte führen Sie die Anwendung als Administrator aus, um auf Temperaturdaten zuzugreifen.</string> <string name="no_temp_data_admin_required">Das Lesen von Temperatursensoren erfordert Administratorrechte. Bitte führen Sie die Anwendung als Administrator aus, um auf Temperaturdaten zuzugreifen.</string>
<string name="temp_admin_required_snackbar">Bitte als Administrator ausführen, um Temperaturen abzurufen</string>
<string name="temp_thermal_state">Thermischer Zustand: %1$s</string> <string name="temp_thermal_state">Thermischer Zustand: %1$s</string>
<string name="temp_thermal_state_nominal">Nominal</string> <string name="temp_thermal_state_nominal">Nominal</string>
<string name="temp_thermal_state_fair">Gut</string> <string name="temp_thermal_state_fair">Gut</string>
@@ -220,6 +220,7 @@
<!--Temp--> <!--Temp-->
<string name="no_temp_data">Lo siento, no puedo encontrar sensores de temperatura :-(</string> <string name="no_temp_data">Lo siento, no puedo encontrar sensores de temperatura :-(</string>
<string name="no_temp_data_admin_required">La lectura de los sensores de temperatura requiere privilegios de administrador. Por favor, ejecute la aplicación como administrador para acceder a los datos de temperatura.</string> <string name="no_temp_data_admin_required">La lectura de los sensores de temperatura requiere privilegios de administrador. Por favor, ejecute la aplicación como administrador para acceder a los datos de temperatura.</string>
<string name="temp_admin_required_snackbar">Por favor, ejecute como administrador para obtener las temperaturas</string>
<string name="temp_thermal_state">Estado térmico: %1$s</string> <string name="temp_thermal_state">Estado térmico: %1$s</string>
<string name="temp_thermal_state_nominal">Nominal</string> <string name="temp_thermal_state_nominal">Nominal</string>
<string name="temp_thermal_state_fair">Aceptable</string> <string name="temp_thermal_state_fair">Aceptable</string>
@@ -221,6 +221,7 @@
<!--Temp--> <!--Temp-->
<string name="no_temp_data">Przepraszam, nie mogłem znaleźć sensorów temperatury :-(</string> <string name="no_temp_data">Przepraszam, nie mogłem znaleźć sensorów temperatury :-(</string>
<string name="no_temp_data_admin_required">Odczyt sensorów temperatury wymaga uprawnień administratora. Uruchom aplikację jako administrator, aby uzyskać dostęp do danych temperatury.</string> <string name="no_temp_data_admin_required">Odczyt sensorów temperatury wymaga uprawnień administratora. Uruchom aplikację jako administrator, aby uzyskać dostęp do danych temperatury.</string>
<string name="temp_admin_required_snackbar">Uruchom jako administrator, aby pobrać dane o temperaturze</string>
<string name="temp_thermal_state">Stan termiczny: %1$s</string> <string name="temp_thermal_state">Stan termiczny: %1$s</string>
<string name="temp_thermal_state_nominal">Nominalny</string> <string name="temp_thermal_state_nominal">Nominalny</string>
<string name="temp_thermal_state_fair">Umiarkowany</string> <string name="temp_thermal_state_fair">Umiarkowany</string>
@@ -263,6 +263,7 @@
<!--Temp--> <!--Temp-->
<string name="no_temp_data">Sorry I can't find temperature sensors :-(</string> <string name="no_temp_data">Sorry I can't find temperature sensors :-(</string>
<string name="no_temp_data_admin_required">Reading temperature sensors requires administrator privileges. Please run the application as administrator to access temperature data.</string> <string name="no_temp_data_admin_required">Reading temperature sensors requires administrator privileges. Please run the application as administrator to access temperature data.</string>
<string name="temp_admin_required_snackbar">Please run as administrator to fetch temperatures</string>
<string name="celsius" translatable="false">&#176;C</string> <string name="celsius" translatable="false">&#176;C</string>
<string name="fahrenheit" translatable="false">&#176;F</string> <string name="fahrenheit" translatable="false">&#176;F</string>
<string name="kelvin" translatable="false">K</string> <string name="kelvin" translatable="false">K</string>
@@ -36,6 +36,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -63,8 +65,10 @@ import com.kgurgul.cpuinfo.shared.ic_battery
import com.kgurgul.cpuinfo.shared.ic_cpu_temp import com.kgurgul.cpuinfo.shared.ic_cpu_temp
import com.kgurgul.cpuinfo.shared.no_temp_data import com.kgurgul.cpuinfo.shared.no_temp_data
import com.kgurgul.cpuinfo.shared.no_temp_data_admin_required import com.kgurgul.cpuinfo.shared.no_temp_data_admin_required
import com.kgurgul.cpuinfo.shared.temp_admin_required_snackbar
import com.kgurgul.cpuinfo.shared.temperature import com.kgurgul.cpuinfo.shared.temperature
import com.kgurgul.cpuinfo.ui.components.CpuPullToRefreshBox import com.kgurgul.cpuinfo.ui.components.CpuPullToRefreshBox
import com.kgurgul.cpuinfo.ui.components.CpuSnackbar
import com.kgurgul.cpuinfo.ui.components.PrimaryTopAppBar import com.kgurgul.cpuinfo.ui.components.PrimaryTopAppBar
import com.kgurgul.cpuinfo.ui.components.VerticalScrollbar import com.kgurgul.cpuinfo.ui.components.VerticalScrollbar
import com.kgurgul.cpuinfo.ui.theme.CpuInfoTheme import com.kgurgul.cpuinfo.ui.theme.CpuInfoTheme
@@ -108,9 +112,17 @@ fun TemperatureScreen(viewModel: TemperatureViewModel = koinViewModel()) {
@Composable @Composable
fun TemperatureScreen(uiState: TemperatureViewModel.UiState) { fun TemperatureScreen(uiState: TemperatureViewModel.UiState) {
val snackbarHostState = remember { SnackbarHostState() }
val adminRequiredMessage = stringResource(Res.string.temp_admin_required_snackbar)
LaunchedEffect(uiState.isAdminRequired) {
if (uiState.isAdminRequired) {
snackbarHostState.showSnackbar(adminRequiredMessage)
}
}
Scaffold( Scaffold(
topBar = { PrimaryTopAppBar(title = stringResource(Res.string.temperature)) }, topBar = { PrimaryTopAppBar(title = stringResource(Res.string.temperature)) },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal), contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal),
snackbarHost = { SnackbarHost(snackbarHostState) { data -> CpuSnackbar(data) } },
) { paddingValues -> ) { paddingValues ->
CpuPullToRefreshBox( CpuPullToRefreshBox(
isRefreshing = uiState.isLoading, isRefreshing = uiState.isLoading,
@@ -0,0 +1,41 @@
/*
* Copyright KG Soft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kgurgul.cpuinfo.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
@Composable
fun <T> ObserveAsEvents(
flow: Flow<T>,
key1: Any? = null,
key2: Any? = null,
state: Lifecycle.State = Lifecycle.State.STARTED,
onEvent: suspend (T) -> Unit,
) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(lifecycleOwner.lifecycle, key1, key2) {
lifecycleOwner.repeatOnLifecycle(state) {
withContext(Dispatchers.Main.immediate) { flow.collect(onEvent) }
}
}
}