Add logs communicate and display on log screen
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ import kotlin.time.Clock
|
||||
class LogRepository(
|
||||
private val dbQueries: AppDatabaseQueries
|
||||
) {
|
||||
suspend fun addLog(title: String, content: String, deviceId: String? = null) {
|
||||
fun addLog(title: String, content: String, deviceId: String? = null) {
|
||||
val source = if (deviceId == null) LogFilter.System.name else LogFilter.Device.name
|
||||
dbQueries.insertLog(
|
||||
title = title,
|
||||
|
||||
+1
-1
@@ -63,5 +63,5 @@ val viewModelModule = module {
|
||||
factoryOf(::SettingViewModel)
|
||||
factoryOf(::BondedViewModel)
|
||||
factoryOf(::LogViewModel)
|
||||
single { ScannerViewModel(get(), get(), get(), get()) }
|
||||
single { ScannerViewModel(get(), get(), get(), get(), get()) }
|
||||
}
|
||||
|
||||
+2
-2
@@ -130,7 +130,7 @@ fun MainScreen(
|
||||
HomeScreen(navController)
|
||||
}
|
||||
composable(BondedDestination.route) {
|
||||
BondedScreen(navController)
|
||||
BondedScreen()
|
||||
}
|
||||
composable(ScannerDestination.route) {
|
||||
ScannerScreen(onDeviceClick = { device ->
|
||||
@@ -144,7 +144,7 @@ fun MainScreen(
|
||||
val deviceId = backStackEntry.savedStateHandle.get<String>("deviceId") ?: ""
|
||||
UartCommunicationScreen(navController, deviceId)
|
||||
}
|
||||
composable(LogDestination.route) { LogScreen(navController) }
|
||||
composable(LogDestination.route) { LogScreen() }
|
||||
composable(SettingDestination.route) {
|
||||
SettingScreen(navController, isDarkTheme, onThemeToggle)
|
||||
}
|
||||
|
||||
+5
-8
@@ -45,7 +45,6 @@ import torquevaultkmp.composeapp.generated.resources.bonded_title
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BondedScreen(
|
||||
navController: NavController,
|
||||
viewModel: BondedViewModel = koinViewModel()
|
||||
) {
|
||||
val devices by viewModel.bondedDevices.collectAsState()
|
||||
@@ -57,7 +56,6 @@ fun BondedScreen(
|
||||
BondedContent(
|
||||
devices = devices,
|
||||
isLoading = isLoading,
|
||||
onAddFake = { viewModel.addFakeDevice() },
|
||||
onDelete = { viewModel.removeDevice(it.id) },
|
||||
onLoadMore = { viewModel.loadNextPage() },
|
||||
onRefresh = { viewModel.refresh() }
|
||||
@@ -69,7 +67,6 @@ fun BondedScreen(
|
||||
internal fun BondedContent(
|
||||
devices: List<Device>,
|
||||
isLoading: Boolean,
|
||||
onAddFake: () -> Unit,
|
||||
onDelete: (Device) -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
onRefresh: () -> Unit
|
||||
@@ -101,11 +98,11 @@ internal fun BondedContent(
|
||||
title = {Text(stringResource(Res.string.bonded_title))},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = onAddFake) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Add Fake Device")
|
||||
}
|
||||
},
|
||||
// floatingActionButton = {
|
||||
// FloatingActionButton(onClick = {}) {
|
||||
// Icon(Icons.Default.Add, contentDescription = "Add")
|
||||
// }
|
||||
// },
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
-10
@@ -55,16 +55,6 @@ class BondedViewModel(
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFakeDevice() {
|
||||
viewModelScope.launch {
|
||||
val id = "AA:BB:CC:DD:EE:${Random.nextInt(10, 99)}"
|
||||
val name = "Torque Tool ${Random.nextInt(1, 100)}"
|
||||
deviceRepository.saveDevice(id, name)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeDevice(id: String) {
|
||||
viewModelScope.launch {
|
||||
deviceRepository.deleteDevice(id)
|
||||
|
||||
+20
-7
@@ -9,9 +9,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.outlined.FilterList
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -32,13 +32,13 @@ import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.AppBar
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.Log
|
||||
import com.digitoolsolutions.app.torquevaultkmp.domain.model.LogFilter
|
||||
@@ -47,6 +47,7 @@ import com.digitoolsolutions.app.torquevaultkmp.screens.logs.view.FilterBottomSh
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.logs.view.LogItem
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
@@ -55,19 +56,20 @@ import torquevaultkmp.composeapp.generated.resources.log_title
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LogScreen(
|
||||
navController: NavController,
|
||||
viewModel: LogViewModel = koinViewModel()
|
||||
) {
|
||||
val logs by viewModel.logs.collectAsState()
|
||||
val activeFilter by viewModel.activeFilter.collectAsState()
|
||||
val isLoading by viewModel.isLoading.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.refresh()
|
||||
}
|
||||
LogContent(
|
||||
logs = logs,
|
||||
activeFilter = activeFilter,
|
||||
isLoading = isLoading,
|
||||
onFilterSelected = { viewModel.setFilter(it) },
|
||||
onAddFakeLog = { viewModel.addFakeLog() },
|
||||
onDeleteAllLogs = { viewModel.clearAllLogs() },
|
||||
onLoadMore = { viewModel.loadNextPage() },
|
||||
onRefresh = { viewModel.refresh() }
|
||||
@@ -81,7 +83,6 @@ internal fun LogContent(
|
||||
activeFilter: LogFilter,
|
||||
isLoading: Boolean,
|
||||
onFilterSelected: (LogFilter) -> Unit,
|
||||
onAddFakeLog: () -> Unit,
|
||||
onDeleteAllLogs: () -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
onRefresh: () -> Unit
|
||||
@@ -91,6 +92,7 @@ internal fun LogContent(
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Infinite Scroll logic
|
||||
val shouldLoadMore = remember {
|
||||
@@ -100,6 +102,12 @@ internal fun LogContent(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(logs) {
|
||||
if (logs.isNotEmpty()) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMore) {
|
||||
snapshotFlow { shouldLoadMore.value }
|
||||
.distinctUntilChanged()
|
||||
@@ -133,8 +141,13 @@ internal fun LogContent(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = onAddFakeLog) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Add Fake Log")
|
||||
FloatingActionButton(onClick = {
|
||||
onRefresh()
|
||||
coroutineScope.launch {
|
||||
listState.animateScrollToItem(0)
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Rounded.KeyboardArrowUp, contentDescription = "Go to top")
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
|
||||
+1
-14
@@ -57,7 +57,7 @@ class LogViewModel(
|
||||
val lastLog = newLogs.last()
|
||||
lastTimestamp = lastLog.timestamp
|
||||
lastId = lastLog.id
|
||||
_logs.value = _logs.value + newLogs
|
||||
_logs.value += newLogs
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
@@ -73,19 +73,6 @@ class LogViewModel(
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun addFakeLog() {
|
||||
viewModelScope.launch {
|
||||
val titles = listOf("System Alert", "Device Connected", "Torque Applied", "Calibration Sync")
|
||||
val contents = listOf("Voltage stable", "Tool #42 joined", "15.5 Nm recorded", "Sync successful")
|
||||
logRepository.addLog(
|
||||
title = titles.random(),
|
||||
content = contents.random()
|
||||
)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAllLogs() {
|
||||
viewModelScope.launch {
|
||||
logRepository.clearLogs()
|
||||
|
||||
+12
-4
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.network.dto.SendMeasureDto
|
||||
import com.digitoolsolutions.app.torquevaultkmp.kable.BleManager
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.repository.DeviceRepository
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.repository.LogRepository
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.repository.ScannerRepository
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
|
||||
@@ -17,6 +18,7 @@ import com.juul.kable.State
|
||||
import com.digitoolsolutions.app.torquevaultkmp.domain.model.WorkOrder
|
||||
import com.digitoolsolutions.app.torquevaultkmp.domain.model.computeWheelsNuts
|
||||
import com.digitoolsolutions.app.torquevaultkmp.utils.Helper
|
||||
import com.juul.kable.ExperimentalApi
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -41,8 +43,9 @@ import kotlin.uuid.ExperimentalUuidApi
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
class ScannerViewModel(
|
||||
private val bleManager: BleManager,
|
||||
private val storage: AppStorage,
|
||||
storage: AppStorage,
|
||||
private val deviceRepository: DeviceRepository,
|
||||
private val logRepository: LogRepository,
|
||||
private val scannerRepository: ScannerRepository
|
||||
) : ViewModel() {
|
||||
companion object {
|
||||
@@ -118,6 +121,7 @@ class ScannerViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
logRepository.addLog(title="Scanner status", content="Scanning ...")
|
||||
}
|
||||
|
||||
private fun startCleanupJob() {
|
||||
@@ -181,17 +185,19 @@ class ScannerViewModel(
|
||||
sendCommand(peripheral, command)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendCommand(peripheral: Peripheral, command: String) {
|
||||
viewModelScope.launch {
|
||||
bleManager.sendCommand(peripheral, Helper.buildUartCommand(command))
|
||||
_rxMessages.value += "Sent: $command"
|
||||
logRepository.addLog(title = "Communicate - Sent", content = command, peripheral.identifier.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(adv: Advertisement) {
|
||||
val deviceIdentifier = adv.identifier.toString()
|
||||
if (connectionJobs[deviceIdentifier]?.isActive == true) return
|
||||
|
||||
logRepository.addLog(title = "Connection - Status", deviceId = deviceIdentifier ,content = "Device [${adv.name}] is connecting...")
|
||||
val job = viewModelScope.launch {
|
||||
try {
|
||||
val peripheral = bleManager.connect(adv)
|
||||
@@ -206,6 +212,7 @@ class ScannerViewModel(
|
||||
/** Add bonded device */
|
||||
addBondedDevice(deviceIdentifier, adv.name ?: "No name")
|
||||
_connectionReasons.value -= deviceIdentifier
|
||||
logRepository.addLog(title = "Connection - Status", deviceId = deviceIdentifier ,content = "Device [${adv.name}] has been connected.")
|
||||
}
|
||||
if (state is State.Disconnected && hasEmitted) {
|
||||
if (_connectionReasons.value[deviceIdentifier] == null) {
|
||||
@@ -233,8 +240,10 @@ class ScannerViewModel(
|
||||
if (e is TimeoutCancellationException) {
|
||||
Napier.e(">>>>> Connection timed out for $deviceIdentifier")
|
||||
_connectionReasons.value += (deviceIdentifier to ConnectionReason.TIMEOUT)
|
||||
logRepository.addLog(title = "Connection - Status", content = "Connect to device [${adv.name}] failed. ${e.message}")
|
||||
} else if (e !is CancellationException) {
|
||||
Napier.e(">>>>> Connection failed for $deviceIdentifier", e)
|
||||
logRepository.addLog(title = "Connection - Status", content = "Connect to device [${adv.name}] failed. ${e.message}")
|
||||
}
|
||||
} finally {
|
||||
_connectedDevices.value -= deviceIdentifier
|
||||
@@ -284,13 +293,12 @@ class ScannerViewModel(
|
||||
}
|
||||
private fun handleMessage(peripheral: Peripheral, msg: String, deviceIdentifier: String) {
|
||||
_rxMessages.value += "Received: $msg"
|
||||
logRepository.addLog(title = "Communicate - Received", content = msg, deviceId = deviceIdentifier)
|
||||
val decoded = Helper.decodeRxData(msg) ?: return
|
||||
val data = decoded.data
|
||||
val woId = decoded.id
|
||||
val cmd = data.take(4)
|
||||
val action = if (cmd == "back") "back" else "next"
|
||||
Napier.d(">>>>> Received data from $deviceIdentifier: $msg", tag="ScannerViewModel.kt")
|
||||
Napier.d(">>>>> Data: $data", tag="ScannerViewModel.kt")
|
||||
viewModelScope.launch {
|
||||
when (cmd) {
|
||||
"requ", "next", "back" -> fetchWorkOrders(peripheral, woId, action)
|
||||
|
||||
Reference in New Issue
Block a user