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