From 5293604ee43d284136b1f30fe68ab139664d4bad Mon Sep 17 00:00:00 2001 From: Vineyro Date: Tue, 6 Aug 2024 17:18:20 +0700 Subject: [PATCH] xlsx export --- app/build.gradle | 4 +- .../inspection/host/view/HostHistory.kt | 63 ++++++++++++--- .../ble/data/repository/ReadHostHistory.kt | 10 +-- .../ble/data/repository/XlsxRepositoryImpl.kt | 72 +++++++++++++++++- .../ble/domain/repository/XlsxRepository.kt | 7 +- .../arma/ble/domain/usecase/ExportToXlsx.kt | 13 +++- app/src/main/res/raw/host_history.xlsx | Bin 0 -> 11361 bytes 7 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/raw/host_history.xlsx diff --git a/app/build.gradle b/app/build.gradle index 4a47d3b..b1d199d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "llc.arma.ble" minSdk 26 targetSdk 34 - versionCode 35 - versionName "1.4.4" + versionCode 39 + versionName "1.4.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/host/view/HostHistory.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/host/view/HostHistory.kt index 87a89a0..a0e6988 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/host/view/HostHistory.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/host/view/HostHistory.kt @@ -25,6 +25,7 @@ import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.Refresh +import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip @@ -38,10 +39,12 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -75,6 +78,7 @@ import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.model.BleName +import llc.arma.ble.domain.usecase.ExportToXlsx import llc.arma.ble.domain.usecase.GetBleBySerial import llc.arma.ble.domain.usecase.GetBleNamesFlow import llc.arma.ble.domain.usecase.GetHostHistoryBySerial @@ -148,6 +152,21 @@ fun HostHistory( }, actions = { + IconButton( + onClick = { + viewModel.setEvent(HostHistoryContract.Event.OnExportHistory(ble.serial)) + }, + enabled = when(state){ + is HostHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished + HostHistoryContract.State.Exception -> true + } + ) { + Icon( + imageVector = Icons.Rounded.Save, + contentDescription = null + ) + } + IconButton( onClick = { viewModel.setEvent(HostHistoryContract.Event.OnRefreshHistory(ble.name, ble.serial)) @@ -183,11 +202,10 @@ val dateFormatter = SimpleDateFormat("dd.MM", Locale.getDefault()) val timeFormatter = SimpleDateFormat("HH:mm", Locale.getDefault()) val colorsStack = listOf( - -0x63d850, -0x98c549, -0xc0ae4b, -0xde690d, - -0xfc560c, -0xff432c, -0xff6978, -0xb350b0, - -0x743cb6, -0x3223c7, -0x14c5, -0x3ef9, - -0x6800, -0xa8de, -0x86aab8, -0x616162, - -0x9f8275, -0xcccccd, -0xbbcca + Color(0xffffd700), Color(0xff2f4f4f), Color(0xff7f0000), Color(0xFFFF0000), + Color(0xffa9a9a9), Color(0xff00fa9a), Color(0xff00ffff), Color(0xfff0e68c), + Color(0xff00bfff), Color(0xff0000ff), Color(0xfff08080), Color(0xffadff2f), + Color(0xffff00ff), Color(0xff4169e1), Color(0xffff1493), Color(0xffee82ee), ) val axisValueFormatter = @@ -246,8 +264,8 @@ fun Display( }.toMap() } - var selectedSerials by remember { - mutableStateOf(allSerials) + var selectedSerials by rememberSaveable { + mutableStateOf(allSerials.take(1)) } val serials = remember(selectedSerials) { allSerials.filter { selectedSerials.contains(it) } } @@ -274,7 +292,7 @@ fun Display( val chart = columnChart( innerSpacing = 2.dp, - columns = serials.map { LineComponent(color = colors[it]!!, thicknessDp = 7f, shape= pillShape) }, + columns = serials.map { LineComponent(color = colors[it]!!.toArgb(), thicknessDp = 7f, shape= pillShape) }, spacing = 8.dp, ) @@ -297,7 +315,7 @@ fun Display( leadingIcon = { Surface( shape = CircleShape, - color = Color(colors[s]!!), + color = colors[s]!!, modifier = Modifier.size(28.dp) ) {} }, @@ -464,7 +482,7 @@ class HostHistoryContract { sealed class Event : ViewEvent { - object StopMeasure : Event() + data object StopMeasure : Event() data class OnStart( val bleName: String, @@ -476,6 +494,10 @@ class HostHistoryContract { val serial: String, ) : Event() + data class OnExportHistory( + val serial: String, + ) : Event() + } sealed class State : ViewState { @@ -502,7 +524,8 @@ class HostHistoryContract { class HostHistoryViewModel @Inject constructor( private val getHostHistoryBySerial: GetHostHistoryBySerial, private val getBleBySerial: GetBleBySerial, - private val getBleNamesFlow: GetBleNamesFlow + private val getBleNamesFlow: GetBleNamesFlow, + private val exportToXlsx: ExportToXlsx ) : BaseViewModel() { var measureJob: Job? = null @@ -520,9 +543,27 @@ class HostHistoryViewModel @Inject constructor( is HostHistoryContract.Event.OnStart -> reduce(viewState.value, event) is HostHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event) is HostHistoryContract.Event.StopMeasure -> reduce(viewState.value, event) + is HostHistoryContract.Event.OnExportHistory -> reduce(viewState.value, event) } } + private fun reduce( + state: HostHistoryContract.State, + event: HostHistoryContract.Event.OnExportHistory + ) { + + if(state is HostHistoryContract.State.Display) { + + if(state.loadingHistoryState is ProgressState.Finished) { + + exportToXlsx.invoke(event.serial, state.loadingHistoryState.data) + + } + + } + + } + private fun reduce( state: HostHistoryContract.State, event: HostHistoryContract.Event.StopMeasure diff --git a/app/src/main/java/llc/arma/ble/data/repository/ReadHostHistory.kt b/app/src/main/java/llc/arma/ble/data/repository/ReadHostHistory.kt index 6e245f4..c903029 100644 --- a/app/src/main/java/llc/arma/ble/data/repository/ReadHostHistory.kt +++ b/app/src/main/java/llc/arma/ble/data/repository/ReadHostHistory.kt @@ -126,10 +126,6 @@ fun readHostHistory( fun getInnerIndex(byte: Byte): Int{ - if(byte != 0.toByte()){ - println(byte) - } - var bits = BitSet.valueOf(byteArrayOf(byte)) bits.clear(0, 4) bits = bits.get(4, 8) @@ -146,7 +142,7 @@ fun readHostHistory( fun getDevType(byte: Byte): Int{ var bits = BitSet.valueOf(byteArrayOf(byte)) - bits.clear(5, 8) + bits.clear(5, 9) val arr = bits.toByteArray() if(arr.isEmpty()){ @@ -161,7 +157,7 @@ fun readHostHistory( var bits = BitSet.valueOf(byteArrayOf(byte)) bits.clear(0, 5) - bits = bits.get(4, 8) + bits = bits.get(5, 8) val arr = bits.toByteArray() if(arr.isEmpty()){ @@ -204,8 +200,10 @@ fun readHostHistory( println("table serial $serial") val devType = getDevType(devTypeByte) + println("devType $devType") val devDataSize = getDevDataSize(devTypeByte) + println("payload $devDataSize") bleTableOffset += 2 if (devDataSize != 0) { diff --git a/app/src/main/java/llc/arma/ble/data/repository/XlsxRepositoryImpl.kt b/app/src/main/java/llc/arma/ble/data/repository/XlsxRepositoryImpl.kt index b413db5..d777f2d 100644 --- a/app/src/main/java/llc/arma/ble/data/repository/XlsxRepositoryImpl.kt +++ b/app/src/main/java/llc/arma/ble/data/repository/XlsxRepositoryImpl.kt @@ -3,7 +3,13 @@ package llc.arma.ble.data.repository import android.app.Application import android.icu.text.SimpleDateFormat import android.os.Environment +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer import llc.arma.ble.R +import llc.arma.ble.app.ui.screen.inspection.host.view.HostEntry +import llc.arma.ble.app.ui.screen.inspection.host.view.colorsStack import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.repository.XlsxRepository import org.apache.poi.ss.usermodel.WorkbookFactory @@ -20,13 +26,13 @@ class XlsxRepositoryImpl @Inject constructor( private val application: Application ) : XlsxRepository { - override fun exportToXls( + override fun exportAccelDataToXls( bleName: String, data: List ): File { - val fileNameDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm") - val fileName = "${fileNameDateFormat.format(Date(System.currentTimeMillis()))}.xlsx".replace(' ', '_') + val fileNameDateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm") + val fileName = "${fileNameDateFormat.format(Date(System.currentTimeMillis()))}.xlsx" val formatter = SimpleDateFormat("dd.MM.yyyy HH:mm") @@ -105,4 +111,64 @@ class XlsxRepositoryImpl @Inject constructor( return mailFile } + override fun exportHostDataToXls(bleName: String, data: List): File { + val fileNameDateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm") + val fileName = "${fileNameDateFormat.format(Date(System.currentTimeMillis()))}.xlsx" + + val formatter = SimpleDateFormat("dd.MM.yyyy HH:mm") + + File(application.filesDir.absolutePath).mkdirs() + val mailFile = File(application.filesDir, fileName) + + mailFile.createNewFile() + + IOUtils.copy(application.resources.openRawResource(R.raw.host_history), FileOutputStream(mailFile)) + + val fileIn = FileInputStream(mailFile) + val workbook = WorkbookFactory.create(fileIn) + val worksheet = workbook.getSheetAt(0) as XSSFSheet + + val row = worksheet.createRow(0) + row.createCell(0).setCellValue("Date") + + data.forEachIndexed { index, historyPoint -> + + row.createCell(index).setCellValue(formatter.format(Date(historyPoint.date))) + + } + + data.flatMap { it.value }.distinct().forEachIndexed { rowIndex, serial -> + + val row = worksheet.createRow(rowIndex + 1) + row.createCell(0).setCellValue(serial) + + data.forEachIndexed { index, historyPoint -> + + row.createCell(index + 1) + .setCellValue( + if(historyPoint.value.contains(serial)) 1.0 else 0.0 + ) + } + + } + + fileIn.close() + val saveFos = FileOutputStream(mailFile) + workbook.write(saveFos) + saveFos.close() + + println(fileName) + + val sharedFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName) + sharedFile.createNewFile() + + val sharedSaveFos = FileOutputStream(sharedFile) + workbook.write(sharedSaveFos) + sharedSaveFos.close() + + workbook.close() + + return mailFile + } + } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/repository/XlsxRepository.kt b/app/src/main/java/llc/arma/ble/domain/repository/XlsxRepository.kt index 5a148d7..72eefb2 100644 --- a/app/src/main/java/llc/arma/ble/domain/repository/XlsxRepository.kt +++ b/app/src/main/java/llc/arma/ble/domain/repository/XlsxRepository.kt @@ -5,9 +5,14 @@ import java.io.File interface XlsxRepository { - fun exportToXls( + fun exportAccelDataToXls( bleName: String, data: List ): File + fun exportHostDataToXls( + bleName: String, + data: List + ): File + } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/ExportToXlsx.kt b/app/src/main/java/llc/arma/ble/domain/usecase/ExportToXlsx.kt index b986769..ebadbe9 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/ExportToXlsx.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/ExportToXlsx.kt @@ -15,7 +15,18 @@ class ExportToXlsx @Inject constructor( data: List ){ - val file = xlsxRepository.exportToXls(bleName, data) + val file = xlsxRepository.exportAccelDataToXls(bleName, data) + emailRepository.sendFile(file) + + } + + @JvmName("invokeHost") + operator fun invoke( + bleName: String, + data: List + ){ + + val file = xlsxRepository.exportHostDataToXls(bleName, data) emailRepository.sendFile(file) } diff --git a/app/src/main/res/raw/host_history.xlsx b/app/src/main/res/raw/host_history.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2a63501090d876aa71a021d3a9d43c6d806719e9 GIT binary patch literal 11361 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 Kl(J~PAN?PU{g=D| literal 0 HcmV?d00001