diff --git a/app/build.gradle b/app/build.gradle index 206b97f7..071f7180 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,7 +60,9 @@ dependencies { implementation project(':profile_hts') implementation project(':profile_prx') implementation project(':profile_rscs') + implementation project(':profile_uart') + implementation project(':profile_dfu') implementation project(":lib_theme") implementation project(":lib_utils") diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt index 5cc9f817..99df98a7 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt @@ -30,6 +30,7 @@ import no.nordicsemi.android.prx.view.PRXScreen import no.nordicsemi.android.rscs.view.RSCSScreen import no.nordicsemi.android.theme.view.CloseIconAppBar import no.nordicsemi.android.uart.view.UARTScreen +import no.nordicsemi.dfu.view.DFUScreen import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen @Composable @@ -115,6 +116,12 @@ internal fun HomeScreen() { 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) } } } + + 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) } + } + } } } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt index 948ecdf4..181ae880 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt @@ -23,5 +23,6 @@ enum class NavDestination(val id: String, val uuid: UUID?, val pairingRequired: PRX("prx-screen", PRX_SERVICE_UUID, true), RSCS("rscs-screen", RSCS_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 } diff --git a/app/src/main/res/drawable/ic_dfu.xml b/app/src/main/res/drawable/ic_dfu.xml new file mode 100644 index 00000000..ed29169b --- /dev/null +++ b/app/src/main/res/drawable/ic_dfu.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68d51077..d252a993 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,4 +17,6 @@ Continuous Glucose UART Serial port over BLE - \ No newline at end of file + DFU + Device Firmware Update + diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUData.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUData.kt index eb8ba2a3..155e306a 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUData.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUData.kt @@ -1,4 +1,13 @@ package no.nordicsemi.dfu.data -class DFUData { -} \ No newline at end of file +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() diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUDataHolder.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUDataHolder.kt index cbe95f47..6d9586ee 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUDataHolder.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUDataHolder.kt @@ -8,7 +8,7 @@ import javax.inject.Singleton @Singleton internal class DFUDataHolder @Inject constructor() { - private val _data = MutableStateFlow(DFUData()) + private val _data = MutableStateFlow(NoFileSelectedState) val data: StateFlow = _data diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUContentView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUContentView.kt index b0d0df2f..5a741fd9 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUContentView.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUContentView.kt @@ -1,8 +1,23 @@ package no.nordicsemi.dfu.view import androidx.compose.runtime.Composable -import no.nordicsemi.dfu.data.DFUData +import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.dfu.data.* @Composable 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 } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUErrorView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUErrorView.kt index 911c5167..d5e8b5ce 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUErrorView.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUErrorView.kt @@ -1,7 +1,18 @@ 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.dfu.R @Composable -fun DFUErrorView() { +internal fun DFUErrorView(onEvent: (DFUViewEvent) -> Unit) { + + Column { + Button(onClick = { onEvent(OnPauseButtonClick) }) { + Text(text = stringResource(id = R.string.dfu_done)) + } + } } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUInstallingView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUInstallingView.kt index c3161e33..d0487e1c 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUInstallingView.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUInstallingView.kt @@ -1,7 +1,27 @@ 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 -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)) + } + } } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt index 6992f962..d7ff6a66 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt @@ -1,7 +1,103 @@ 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.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 -fun DFUSelectFileView() { +internal fun DFUSelectFileView() { + + val result = remember { mutableStateOf(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"), } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSuccessView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSuccessView.kt index d8277efe..f5d68a3c 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSuccessView.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSuccessView.kt @@ -1,7 +1,18 @@ 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.dfu.R @Composable -fun DFUSuccessView() { +internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) { + + Column { + Button(onClick = { onEvent(OnPauseButtonClick) }) { + Text(text = stringResource(id = R.string.dfu_done)) + } + } } diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSummaryView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSummaryView.kt new file mode 100644 index 00000000..6a7bced4 --- /dev/null +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSummaryView.kt @@ -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)) + } + } +} diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUViewEvent.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUViewEvent.kt index 56cb09e4..ef7bd99d 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUViewEvent.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUViewEvent.kt @@ -2,4 +2,10 @@ package no.nordicsemi.dfu.view internal sealed class DFUViewEvent +internal data class OnFileSelected(val uri: String) : DFUViewEvent() + +internal object OnPauseButtonClick : DFUViewEvent() + +internal object OnStopButtonClick : DFUViewEvent() + internal object OnDisconnectButtonClick : DFUViewEvent() diff --git a/profile_dfu/src/main/res/values/strings.xml b/profile_dfu/src/main/res/values/strings.xml index 287bafd3..e60fcc2e 100644 --- a/profile_dfu/src/main/res/values/strings.xml +++ b/profile_dfu/src/main/res/values/strings.xml @@ -1,4 +1,17 @@ DFU + + Done + Pause + Stop + Install + + Select .zip + Select .hex + + File managers + Please select + Confirm + Dismiss \ No newline at end of file