Add views to DFU module

This commit is contained in:
Sylwester Zieliński
2022-01-05 17:36:55 +01:00
parent fca84b7f23
commit c567836d01
15 changed files with 263 additions and 10 deletions

View File

@@ -60,7 +60,9 @@ dependencies {
implementation project(':profile_hts') implementation project(':profile_hts')
implementation project(':profile_prx') implementation project(':profile_prx')
implementation project(':profile_rscs') implementation project(':profile_rscs')
implementation project(':profile_uart') implementation project(':profile_uart')
implementation project(':profile_dfu')
implementation project(":lib_theme") implementation project(":lib_theme")
implementation project(":lib_utils") implementation project(":lib_utils")

View File

@@ -30,6 +30,7 @@ import no.nordicsemi.android.prx.view.PRXScreen
import no.nordicsemi.android.rscs.view.RSCSScreen import no.nordicsemi.android.rscs.view.RSCSScreen
import no.nordicsemi.android.theme.view.CloseIconAppBar import no.nordicsemi.android.theme.view.CloseIconAppBar
import no.nordicsemi.android.uart.view.UARTScreen import no.nordicsemi.android.uart.view.UARTScreen
import no.nordicsemi.dfu.view.DFUScreen
import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen
@Composable @Composable
@@ -115,6 +116,12 @@ internal fun HomeScreen() {
UARTScreen { goHome() } UARTScreen { goHome() }
} }
} }
composable(NavDestination.DFU.id) {
FindDeviceScreen(ParcelUuid(NavDestination.DFU.uuid)) {
deviceHolder.onDeviceSelected(it)
DFUScreen { goHome() }
}
}
} }
} }
@@ -261,6 +268,22 @@ fun HomeView(callback: (NavDestination) -> Unit) {
) { callback(NavDestination.UART) } ) { callback(NavDestination.UART) }
} }
} }
Spacer(modifier = Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
FeatureButton(
R.drawable.ic_uart, R.string.uart_module,
R.string.uart_module_full
) { callback(NavDestination.DFU) }
}
}
} }
} }
} }

View File

@@ -23,5 +23,6 @@ enum class NavDestination(val id: String, val uuid: UUID?, val pairingRequired:
PRX("prx-screen", PRX_SERVICE_UUID, true), PRX("prx-screen", PRX_SERVICE_UUID, true),
RSCS("rscs-screen", RSCS_SERVICE_UUID, false), RSCS("rscs-screen", RSCS_SERVICE_UUID, false),
CGMS("cgms-screen", CGMS_SERVICE_UUID, false), CGMS("cgms-screen", CGMS_SERVICE_UUID, false),
UART("uart-screen", UART_SERVICE_UUID, false); UART("uart-screen", UART_SERVICE_UUID, false),
DFU("dfu-screen", null, false); //todo check characteristic
} }

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="80dp"
android:height="80dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#00B3DC"
android:pathData="M316.8,389.3c-2.2,0 -4,1.8 -4,4v238.3c0,2.2 1.8,4 4,4h49.5c40.1,0 61.9,-21.8 61.9,-62.3V451.5c0,-40.5 -21.8,-62.3 -61.9,-62.3H316.8zM390.9,454v116.8c0,20.8 -8.1,30.3 -26,30.3h-10.8c-2.2,0 -4,-1.8 -4,-4V427.8c0,-2.2 1.8,-4 4,-4h10.8C382.8,423.8 390.9,433.3 390.9,454z" />
<path
android:fillColor="#00B3DC"
android:pathData="M565.2,389.3h-95.1c-2.2,0 -4,1.8 -4,4v238.3c0,2.2 1.8,4 4,4h29.3c2.2,0 4,-1.8 4,-4v-99.6c0,-2.2 1.8,-4 4,-4h44.7c2.2,0 4,-1.8 4,-4v-26.4c0,-2.2 -1.8,-4 -4,-4h-44.7c-2.2,0 -4,-1.8 -4,-4v-61.6c0,-2.2 1.8,-4 4,-4h57.8c2.2,0 4,-1.8 4,-4v-26.4C569.2,391.1 567.4,389.3 565.2,389.3z" />
<path
android:fillColor="#00B3DC"
android:pathData="M714,580.3v-187c0,-2.2 -1.8,-4 -4,-4h-29.3c-2.2,0 -4,1.8 -4,4v186c0,17.9 -8.1,24.6 -21.1,24.6c-13,0 -21.1,-6.7 -21.1,-24.6v-186c0,-2.2 -1.8,-4 -4,-4h-27.8c-2.2,0 -4,1.8 -4,4v187c0,38.7 22.2,58.8 57.4,58.8C691.8,639.1 714,619 714,580.3z" />
<path
android:fillColor="#00B3DC"
android:pathData="M410.7,821.8c-3.4,-4.6 -12,-5.4 -13.5,-1.3l-13.9,38.5C191.3,787.7 92,574.1 161.8,381.2c34,-93.9 102.5,-169 192.9,-211.4c14.3,-6.7 20.4,-23.7 13.7,-37.9c-6.7,-14.3 -23.7,-20.4 -37.9,-13.7c-50.7,23.7 -95.6,56.5 -133.4,97.4c-39.2,42.3 -69.1,91.5 -88.9,146.2C88.4,416.5 79.9,473.5 83,531.1c3,55.6 16.5,109.5 40.2,160.2c23.7,50.7 56.5,95.6 97.4,133.4c41.6,38.5 89.8,68.1 143.3,87.8l-12.8,35.4c-1.5,4.1 5.8,9 11.3,7.6l119.7,-29.7c3.3,-0.8 4.2,-3.6 2.2,-6.3L410.7,821.8z" />
<path
android:fillColor="#00B3DC"
android:pathData="M943.8,485.8c-3,-55.6 -16.5,-109.5 -40.2,-160.2c-23.7,-50.7 -56.5,-95.6 -97.4,-133.4c-40.7,-37.7 -87.8,-66.8 -140,-86.6l10.7,-29.5c1.5,-4.1 -5.8,-9 -11.3,-7.6L545.8,98.1c-3.3,0.8 -4.2,3.6 -2.2,6.3l73.6,97.8c3.4,4.6 12,5.4 13.5,1.3l16,-44.4C836.6,231.6 934.3,443.8 865,635.6C831,729.5 762.5,804.6 672,847c-14.3,6.7 -20.4,23.7 -13.7,37.9c4.8,10.3 15.1,16.4 25.8,16.4c4.1,0 8.2,-0.9 12.1,-2.7c50.7,-23.7 95.6,-56.5 133.4,-97.4c39.2,-42.3 69.1,-91.5 88.9,-146.2S946.8,543.4 943.8,485.8z" />
</vector>

View File

@@ -17,4 +17,6 @@
<string name="cgm_module_full">Continuous Glucose</string> <string name="cgm_module_full">Continuous Glucose</string>
<string name="uart_module">UART</string> <string name="uart_module">UART</string>
<string name="uart_module_full">Serial port over BLE</string> <string name="uart_module_full">Serial port over BLE</string>
<string name="dfu_module">DFU</string>
<string name="dfu_module_full">Device Firmware Update</string>
</resources> </resources>

View File

@@ -1,4 +1,13 @@
package no.nordicsemi.dfu.data package no.nordicsemi.dfu.data
class DFUData { import java.io.File
}
internal sealed class DFUData
internal object NoFileSelectedState : DFUData()
internal data class FileReadyState(val file: File, val isUploading: Boolean) : DFUData()
internal object UploadSuccessState : DFUData()
internal object UploadFailureState : DFUData()

View File

@@ -8,7 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
internal class DFUDataHolder @Inject constructor() { internal class DFUDataHolder @Inject constructor() {
private val _data = MutableStateFlow(DFUData()) private val _data = MutableStateFlow(NoFileSelectedState)
val data: StateFlow<DFUData> = _data val data: StateFlow<DFUData> = _data

View File

@@ -1,8 +1,23 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import no.nordicsemi.dfu.data.DFUData import no.nordicsemi.android.utils.exhaustive
import no.nordicsemi.dfu.data.*
@Composable @Composable
internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) { internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
when (state) {
NoFileSelectedState -> DFUSelectFileView()
is FileReadyState -> FileReadyView(state, onEvent)
UploadFailureState -> DFUErrorView(onEvent)
UploadSuccessState -> DFUSuccessView(onEvent)
}.exhaustive
}
@Composable
private fun FileReadyView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
when (state.isUploading) {
true -> DFUInstallingView(onEvent)
false -> DFUSummaryView(onEvent)
}.exhaustive
} }

View File

@@ -1,7 +1,18 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.dfu.R
@Composable @Composable
fun DFUErrorView() { internal fun DFUErrorView(onEvent: (DFUViewEvent) -> Unit) {
Column {
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_done))
}
}
} }

View File

@@ -1,7 +1,27 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.material.you.CircularProgressIndicator
import no.nordicsemi.dfu.R
@Composable @Composable
fun DFUInstallingView() { internal fun DFUInstallingView(onEvent: (DFUViewEvent) -> Unit) {
Column {
CircularProgressIndicator()
//todo add percentage indicator
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_pause))
}
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_stop))
}
}
} }

View File

@@ -1,7 +1,103 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.dfu.DfuBaseService
import no.nordicsemi.android.utils.EMPTY
import no.nordicsemi.dfu.R
@Composable @Composable
fun DFUSelectFileView() { internal fun DFUSelectFileView() {
val result = remember { mutableStateOf<Uri?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
result.value = it
}
Row {
Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_ZIP) }) {
Text(text = stringResource(id = R.string.dfu_select_zip))
}
Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_OCTET_STREAM) }) {
Text(text = stringResource(id = R.string.dfu_select_hex))
}
}
}
@Composable
fun ChooseFileMangerDialog(onDismiss: () -> Unit) {
val alias = remember { mutableStateOf(String.EMPTY) }
val command = remember { mutableStateOf(String.EMPTY) }
val context = LocalContext.current
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(text = stringResource(id = R.string.dfu_macro_dialog_title))
},
text = {
Column {
Text(stringResource(id = R.string.dfu_macro_dialog_info))
FileManagerOption.values().forEach {
FileManagerItem(item = it) {
openFileMangerPlayStore(context, it.url)
}
}
}
},
confirmButton = {
TextButton(
onClick = { onDismiss() }
) {
Text(stringResource(id = R.string.dfu_macro_dialog_dismiss))
}
},
dismissButton = {
}
)
}
private fun openFileMangerPlayStore(context: Context, url: String) {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
@Composable
private fun FileManagerItem(item: FileManagerOption, onItemSelected: (FileManagerOption) -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { onItemSelected(item) }
) {
Text(text = item.title)
}
}
enum class FileManagerOption(val title: String, val url: String) {
DRIVE("Drive", "market://details?id=com.google.android.apps.docs"),
FILE_MANAGER("File Manager", "market://details?id=com.rhmsoft.fm"),
TOTAL_COMMANDER("Total Commander", "market://details?id=com.ghisler.android.TotalCommander"),
OTHERS("Search for others", "market://search?q=file manager"),
} }

View File

@@ -1,7 +1,18 @@
package no.nordicsemi.dfu.view package no.nordicsemi.dfu.view
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.dfu.R
@Composable @Composable
fun DFUSuccessView() { internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) {
Column {
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_done))
}
}
} }

View File

@@ -0,0 +1,23 @@
package no.nordicsemi.dfu.view
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.material.you.CircularProgressIndicator
import no.nordicsemi.dfu.R
@Composable
internal fun DFUSummaryView(onEvent: (DFUViewEvent) -> Unit) {
Column {
CircularProgressIndicator()
//todo add percentage indicator
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_install))
}
}
}

View File

@@ -2,4 +2,10 @@ package no.nordicsemi.dfu.view
internal sealed class DFUViewEvent internal sealed class DFUViewEvent
internal data class OnFileSelected(val uri: String) : DFUViewEvent()
internal object OnPauseButtonClick : DFUViewEvent()
internal object OnStopButtonClick : DFUViewEvent()
internal object OnDisconnectButtonClick : DFUViewEvent() internal object OnDisconnectButtonClick : DFUViewEvent()

View File

@@ -1,4 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="dfu_title">DFU</string> <string name="dfu_title">DFU</string>
<string name="dfu_done">Done</string>
<string name="dfu_pause">Pause</string>
<string name="dfu_stop">Stop</string>
<string name="dfu_install">Install</string>
<string name="dfu_select_zip">Select .zip</string>
<string name="dfu_select_hex">Select .hex</string>
<string name="dfu_macro_dialog_title">File managers</string>
<string name="dfu_macro_dialog_info">Please select </string>
<string name="dfu_macro_dialog_confirm">Confirm</string>
<string name="dfu_macro_dialog_dismiss">Dismiss</string>
</resources> </resources>