diff --git a/profile_dfu/src/main/AndroidManifest.xml b/profile_dfu/src/main/AndroidManifest.xml
index 47115554..ee941bfa 100644
--- a/profile_dfu/src/main/AndroidManifest.xml
+++ b/profile_dfu/src/main/AndroidManifest.xml
@@ -2,4 +2,7 @@
+
+
+
\ No newline at end of file
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 05baab23..33cc3b62 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,16 +1,24 @@
package no.nordicsemi.dfu.data
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
-import java.io.File
internal sealed class DFUData
-internal object NoFileSelectedState : DFUData()
+internal data class NoFileSelectedState(
+ val isError: Boolean = false
+) : DFUData()
internal data class FileReadyState(
- val file: File,
- val device: DiscoveredBluetoothDevice,
- val isUploading: Boolean = false
+ val file: DFUFile,
+ val device: DiscoveredBluetoothDevice
+) : DFUData()
+
+internal data class HexFileReadyState(
+ val file: DFUFile
+) : DFUData()
+
+internal data class FileInstallingState(
+ val status: DFUServiceStatus = Idle
) : DFUData()
internal object UploadSuccessState : DFUData()
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFile.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFile.kt
new file mode 100644
index 00000000..7137dac3
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFile.kt
@@ -0,0 +1,24 @@
+package no.nordicsemi.dfu.data
+
+import android.net.Uri
+
+sealed class DFUFile {
+ abstract val fileType: DFUFileType
+}
+
+data class ZipFile(val data: FileData) : DFUFile() {
+ override val fileType: DFUFileType = DFUFileType.TYPE_AUTO
+}
+
+data class HexFile(
+ val data: FileData,
+ val datFileData: FileData,
+ override val fileType: DFUFileType
+) : DFUFile()
+
+data class FileData(
+ val uri: Uri,
+ val name: String,
+ val path: String,
+ val size: Long
+)
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileManager.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileManager.kt
new file mode 100644
index 00000000..868d8b39
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileManager.kt
@@ -0,0 +1,56 @@
+package no.nordicsemi.dfu.data
+
+import android.content.Context
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.core.net.toFile
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class DFUFileManager @Inject constructor(
+ @ApplicationContext
+ private val context: Context
+) {
+
+ fun createFile(uri: Uri): FileData? {
+ return try {
+ createFromFile(uri)
+ } catch (e: Exception) {
+ try {
+ createFromContentResolver(uri)
+ } catch (e: Exception) {
+ null
+ }
+ }
+ }
+
+ private fun createFromFile(uri: Uri): FileData {
+ val file = uri.toFile()
+ return FileData(uri, file.name, file.path, file.length())
+ }
+
+ private fun createFromContentResolver(uri: Uri): FileData? {
+ return try {
+ val data = context.contentResolver.query(uri, null, null, null, null)
+
+ if (data != null && data.moveToNext()) {
+
+ val displayNameIndex = data.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
+ val fileSizeIndex = data.getColumnIndex(MediaStore.MediaColumns.SIZE)
+ val dataIndex = data.getColumnIndex(MediaStore.MediaColumns.DATA)
+
+ val fileName = data.getString(displayNameIndex)
+ val fileSize = data.getInt(fileSizeIndex)
+ val filePath = data.getString(dataIndex)
+
+ data.close()
+
+ FileData(uri, fileName, filePath, fileSize.toLong())
+ } else {
+ null
+ }
+ } catch (e: Exception) {
+ null
+ }
+ }
+}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileType.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileType.kt
new file mode 100644
index 00000000..37f79065
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUFileType.kt
@@ -0,0 +1,14 @@
+package no.nordicsemi.dfu.data
+
+enum class DFUFileType(val id: Int) {
+ TYPE_AUTO(0x00),
+ TYPE_SOFT_DEVICE(0x01),
+ TYPE_BOOTLOADER(0x02),
+ TYPE_APPLICATION(0x04);
+
+ companion object {
+ fun create(id: Int): DFUFileType? {
+ return values().find { it.id == id }
+ }
+ }
+}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUManager.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUManager.kt
new file mode 100644
index 00000000..eb83dea4
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUManager.kt
@@ -0,0 +1,37 @@
+package no.nordicsemi.dfu.data
+
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import no.nordicsemi.android.dfu.DfuServiceInitiator
+import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
+import no.nordicsemi.dfu.repository.DFUService
+import no.nordicsemi.ui.scanner.ui.exhaustive
+import javax.inject.Inject
+
+class DFUManager @Inject constructor(
+ @ApplicationContext
+ private val context: Context,
+ private val deviceHolder: SelectedBluetoothDeviceHolder
+) {
+
+ fun install(file: DFUFile) {
+ val device = deviceHolder.device!!
+
+ val starter = DfuServiceInitiator(device.address)
+ .setDeviceName(device.displayName())
+// .setKeepBond(keepBond)
+// .setForceDfu(forceDfu)
+// .setPacketsReceiptNotificationsEnabled(enablePRNs)
+// .setPacketsReceiptNotificationsValue(numberOfPackets)
+ .setPrepareDataObjectDelay(400)
+ .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
+
+ when (file) {
+ is ZipFile -> starter.setZip(file.uri, file.path)
+ is HexFile -> starter.setBinOrHex(file.fileType.id, file.uri, file.path)
+ .setInitFile(file.datFile.uri, file.datFile.path)
+ }.exhaustive
+
+ starter.start(context, DFUService::class.java)
+ }
+}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUProgressManager.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUProgressManager.kt
new file mode 100644
index 00000000..0ebebea1
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUProgressManager.kt
@@ -0,0 +1,86 @@
+package no.nordicsemi.dfu.data
+
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.scopes.ViewModelScoped
+import kotlinx.coroutines.flow.MutableStateFlow
+import no.nordicsemi.android.dfu.DfuProgressListenerAdapter
+import no.nordicsemi.android.dfu.DfuServiceListenerHelper
+import javax.inject.Inject
+
+@ViewModelScoped
+internal class DFUProgressManager @Inject constructor(
+ @ApplicationContext
+ private val context: Context
+) : DfuProgressListenerAdapter() {
+
+ val status = MutableStateFlow(Idle)
+
+ override fun onDeviceConnecting(deviceAddress: String) {
+ status.value = Connecting
+ }
+
+ override fun onDeviceConnected(deviceAddress: String) {
+ status.value = Connected
+ }
+
+ override fun onDfuProcessStarting(deviceAddress: String) {
+ status.value = Starting
+ }
+
+ override fun onDfuProcessStarted(deviceAddress: String) {
+ status.value = Started
+ }
+
+ override fun onEnablingDfuMode(deviceAddress: String) {
+ status.value = EnablingDfu
+ }
+
+ override fun onProgressChanged(
+ deviceAddress: String,
+ percent: Int,
+ speed: Float,
+ avgSpeed: Float,
+ currentPart: Int,
+ partsTotal: Int
+ ) {
+ status.value = ProgressUpdate(percent)
+ }
+
+ override fun onFirmwareValidating(deviceAddress: String) {
+ status.value = Validating
+ }
+
+ override fun onDeviceDisconnecting(deviceAddress: String?) {
+ status.value = Disconnecting
+ }
+
+ override fun onDeviceDisconnected(deviceAddress: String) {
+ status.value = Disconnected
+ }
+
+ override fun onDfuCompleted(deviceAddress: String) {
+ status.value = Completed
+ }
+
+ override fun onDfuAborted(deviceAddress: String) {
+ status.value = Aborted
+ }
+
+ override fun onError(
+ deviceAddress: String,
+ error: Int,
+ errorType: Int,
+ message: String?
+ ) {
+ status.value = Error(message)
+ }
+
+ fun registerListener() {
+ DfuServiceListenerHelper.registerProgressListener(context, this)
+ }
+
+ fun unregisterListener() {
+ DfuServiceListenerHelper.unregisterProgressListener(context, this)
+ }
+}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFURepository.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFURepository.kt
index 3c288471..89de121a 100644
--- a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFURepository.kt
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFURepository.kt
@@ -3,7 +3,6 @@ package no.nordicsemi.dfu.data
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
-import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@@ -12,15 +11,18 @@ internal class DFURepository @Inject constructor(
private val deviceHolder: SelectedBluetoothDeviceHolder
) {
- private val _data = MutableStateFlow(NoFileSelectedState)
+ private val _data = MutableStateFlow(NoFileSelectedState())
val data: StateFlow = _data
- fun initFile(file: File) {
- _data.value = FileReadyState(file, deviceHolder.device!!)
+ fun initFile(file: DFUFile?) {
+ if (file == null) {
+ _data.value = NoFileSelectedState(true)
+ } else {
+ _data.value = FileReadyState(file, deviceHolder.device!!)
+ }
}
fun install() {
- val state = _data.value as FileReadyState
- _data.value = state.copy(isUploading = true)
+ _data.value = FileInstallingState()
}
}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUServiceStatus.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUServiceStatus.kt
new file mode 100644
index 00000000..bc011d09
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUServiceStatus.kt
@@ -0,0 +1,17 @@
+package no.nordicsemi.dfu.data
+
+internal sealed class DFUServiceStatus
+
+internal object Idle : DFUServiceStatus()
+internal object Connecting : DFUServiceStatus()
+internal object Connected : DFUServiceStatus()
+internal object Starting : DFUServiceStatus()
+internal object Started : DFUServiceStatus()
+internal object EnablingDfu : DFUServiceStatus()
+internal data class ProgressUpdate(val progress: Int): DFUServiceStatus()
+internal object Validating : DFUServiceStatus()
+internal object Disconnecting : DFUServiceStatus()
+internal object Disconnected : DFUServiceStatus()
+internal object Completed : DFUServiceStatus()
+internal object Aborted : DFUServiceStatus()
+internal data class Error(val message: String?): DFUServiceStatus()
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/repository/DFUService.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/repository/DFUService.kt
index 634c8a73..8acb01de 100644
--- a/profile_dfu/src/main/java/no/nordicsemi/dfu/repository/DFUService.kt
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/repository/DFUService.kt
@@ -48,4 +48,4 @@ class DFUService : DfuBaseService() {
// return BuildConfig.DEBUG;
return true
}
-}
\ No newline at end of file
+}
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 779aeab1..27371fff 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
@@ -12,18 +12,12 @@ import no.nordicsemi.dfu.data.*
internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
Box(modifier = Modifier.padding(16.dp)) {
when (state) {
- NoFileSelectedState -> DFUSelectFileView(onEvent)
- is FileReadyState -> FileReadyView(state, onEvent)
+ is NoFileSelectedState -> DFUSelectMainFileView(state, onEvent)
+ is FileReadyState -> DFUSummaryView(state, onEvent)
UploadSuccessState -> DFUSuccessView(onEvent)
UploadFailureState -> DFUErrorView(onEvent)
+ is FileInstallingState -> DFUInstallingView(state, onEvent)
+ is HexFileReadyState -> DFUSelectDatFileView(onEvent)
}.exhaustive
}
}
-
-@Composable
-private fun FileReadyView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
- when (state.isUploading) {
- false -> DFUSummaryView(state, onEvent)
- true -> DFUInstallingView(state, onEvent)
- }.exhaustive
-}
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 865af584..62869519 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,68 +1,42 @@
package no.nordicsemi.dfu.view
-import android.content.Context
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.core.net.toUri
-import no.nordicsemi.android.dfu.DfuServiceInitiator
+import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.CircularProgressIndicator
import no.nordicsemi.dfu.R
-import no.nordicsemi.dfu.data.FileReadyState
-import no.nordicsemi.dfu.repository.DFUService
+import no.nordicsemi.dfu.data.FileInstallingState
@Composable
-internal fun DFUInstallingView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
-
- Column {
+internal fun DFUInstallingView(state: FileInstallingState, onEvent: (DFUViewEvent) -> Unit) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
CircularProgressIndicator()
- //todo add percentage indicator
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(text = state.status.toDisplayString())
+
+ Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_pause))
}
+ Spacer(modifier = Modifier.height(16.dp))
+
Button(onClick = { onEvent(OnPauseButtonClick) }) {
Text(text = stringResource(id = R.string.dfu_stop))
}
}
-
- val context = LocalContext.current
- LaunchedEffect(state.isUploading) {
- if (state.isUploading) {
- install(context, state)
- }
- }
-}
-
-
-
-private fun install(context: Context, state: FileReadyState) {
-
- val device = state.device
-
- val fileName = state.file.name
- val fileLength = state.file.length()
-
- val starter = DfuServiceInitiator(device.address)
- .setDeviceName(device.displayName())
-// .setKeepBond(keepBond)
-// .setForceDfu(forceDfu)
-// .setPacketsReceiptNotificationsEnabled(enablePRNs)
-// .setPacketsReceiptNotificationsValue(numberOfPackets)
-// .setPrepareDataObjectDelay(400)
-// .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
-// if (fileType == DfuService.TYPE_AUTO) {
- starter.setZip(state.file.toUri(), state.file.path)
-// if (scope != null) starter.setScope(scope)
-// } else {
-// starter.setBinOrHex(fileType, fileStreamUri, filePath)
-// .setInitFile(initFileStreamUri, initFilePath)
-// }
- starter.start(context, DFUService::class.java)
}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUMappers.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUMappers.kt
new file mode 100644
index 00000000..cd19de20
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUMappers.kt
@@ -0,0 +1,40 @@
+package no.nordicsemi.dfu.view
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import no.nordicsemi.dfu.R
+import no.nordicsemi.dfu.data.Aborted
+import no.nordicsemi.dfu.data.Completed
+import no.nordicsemi.dfu.data.Connected
+import no.nordicsemi.dfu.data.Connecting
+import no.nordicsemi.dfu.data.DFUServiceStatus
+import no.nordicsemi.dfu.data.Disconnected
+import no.nordicsemi.dfu.data.Disconnecting
+import no.nordicsemi.dfu.data.EnablingDfu
+import no.nordicsemi.dfu.data.Error
+import no.nordicsemi.dfu.data.Idle
+import no.nordicsemi.dfu.data.ProgressUpdate
+import no.nordicsemi.dfu.data.Started
+import no.nordicsemi.dfu.data.Starting
+import no.nordicsemi.dfu.data.Validating
+
+@Composable
+internal fun DFUServiceStatus.toDisplayString(): String {
+ val displayStatus = when (this) {
+ Aborted -> stringResource(id = R.string.dfu_display_status_aborted)
+ Completed -> stringResource(id = R.string.dfu_display_status_completed)
+ Connected -> stringResource(id = R.string.dfu_display_status_connected)
+ Connecting -> stringResource(id = R.string.dfu_display_status_connecting)
+ Disconnected -> stringResource(id = R.string.dfu_display_status_disconnected)
+ Disconnecting -> stringResource(id = R.string.dfu_display_status_disconnecting)
+ EnablingDfu -> stringResource(id = R.string.dfu_display_status_enabling)
+ is Error -> message ?: stringResource(id = R.string.dfu_display_status_error)
+ Idle -> stringResource(id = R.string.dfu_display_status_idle)
+ is ProgressUpdate -> stringResource(id = R.string.dfu_display_status_progress_update, progress)
+ Started -> stringResource(id = R.string.dfu_display_status_started)
+ Starting -> stringResource(id = R.string.dfu_display_status_starting)
+ Validating -> stringResource(id = R.string.dfu_display_status_validating)
+ }
+
+ return stringResource(id = R.string.dfu_display_status, displayStatus)
+}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt
index f657d6c8..d5664ce2 100644
--- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUScreen.kt
@@ -14,11 +14,12 @@ import no.nordicsemi.dfu.R
import no.nordicsemi.dfu.data.DFUData
import no.nordicsemi.dfu.repository.DFUService
import no.nordicsemi.dfu.viewmodel.DFUViewModel
+import no.nordicsemi.dfu.data.NoFileSelectedState
@Composable
fun DFUScreen(finishAction: () -> Unit) {
val viewModel: DFUViewModel = hiltViewModel()
- val state = viewModel.state.collectAsState().value
+ val state = viewModel.state.collectAsState(NoFileSelectedState).value
val isScreenActive = viewModel.isActive.collectAsState().value
val context = LocalContext.current
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectDatFileView.kt
similarity index 58%
rename from profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt
rename to profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectDatFileView.kt
index e764ee3a..a6bd34c7 100644
--- a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectFileView.kt
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectDatFileView.kt
@@ -2,7 +2,8 @@ package no.nordicsemi.dfu.view
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
@@ -18,40 +19,28 @@ import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.dfu.R
@Composable
-internal fun DFUSelectFileView(onEvent: (DFUViewEvent) -> Unit) {
+internal fun DFUSelectDatFileView(onEvent: (DFUViewEvent) -> Unit) {
ScreenSection {
- SectionTitle(icon = Icons.Default.Settings, title = stringResource(id = R.string.dfu_choose_file))
+ SectionTitle(
+ icon = Icons.Default.Settings,
+ title = stringResource(id = R.string.dfu_choose_file)
+ )
Spacer(modifier = Modifier.padding(8.dp))
Text(
- text = stringResource(id = R.string.dfu_choose_info),
+ text = stringResource(id = R.string.dfu_choose_dat_info),
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.padding(8.dp))
- ButtonsRow(onEvent)
- }
-}
-
-@Composable
-private fun ButtonsRow(onEvent: (DFUViewEvent) -> Unit) {
-
- val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
- onEvent(OnFileSelected(it!!))
- }
-
- Row(
- horizontalArrangement = Arrangement.SpaceEvenly,
- modifier = Modifier.fillMaxWidth()
- ) {
- Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_ZIP) }) {
- Text(text = stringResource(id = R.string.dfu_select_zip))
+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
+ uri?.let { onEvent(OnDatFileSelected(it)) }
}
Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_OCTET_STREAM) }) {
- Text(text = stringResource(id = R.string.dfu_select_hex))
+ Text(text = stringResource(id = R.string.dfu_select_dat))
}
}
}
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectMainFileView.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectMainFileView.kt
new file mode 100644
index 00000000..a97751c6
--- /dev/null
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/view/DFUSelectMainFileView.kt
@@ -0,0 +1,81 @@
+package no.nordicsemi.dfu.view
+
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import no.nordicsemi.android.dfu.DfuBaseService
+import no.nordicsemi.android.theme.view.ScreenSection
+import no.nordicsemi.android.theme.view.SectionTitle
+import no.nordicsemi.dfu.R
+import no.nordicsemi.dfu.data.DFUData
+
+@Composable
+internal fun DFUSelectMainFileView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
+ ScreenSection {
+ SectionTitle(
+ icon = Icons.Default.Settings,
+ title = stringResource(id = R.string.dfu_choose_file)
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ Text(
+ text = stringResource(id = R.string.dfu_choose_info),
+ style = MaterialTheme.typography.bodyMedium
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ ButtonsRow(onEvent)
+ }
+}
+
+@Composable
+private fun ButtonsRow(onEvent: (DFUViewEvent) -> Unit) {
+
+ val fileType = rememberSaveable { mutableStateOf(DfuBaseService.MIME_TYPE_ZIP) }
+
+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
+ uri?.let {
+ if (fileType.value == DfuBaseService.MIME_TYPE_ZIP) {
+ onEvent(OnZipFileSelected(it))
+ } else {
+ onEvent(OnHexFileSelected(it))
+ }
+ }
+ }
+
+ Row(
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(onClick = {
+ fileType.value = DfuBaseService.MIME_TYPE_ZIP
+ launcher.launch(fileType.value)
+ }) {
+ Text(text = stringResource(id = R.string.dfu_select_zip))
+ }
+
+ Button(onClick = {
+ fileType.value = DfuBaseService.MIME_TYPE_OCTET_STREAM
+ launcher.launch(fileType.value)
+ }) {
+ Text(text = stringResource(id = R.string.dfu_select_hex))
+ }
+ }
+}
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 ec3d30e8..21af155a 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
@@ -4,7 +4,9 @@ import android.net.Uri
internal sealed class DFUViewEvent
-internal data class OnFileSelected(val uri: Uri) : DFUViewEvent()
+internal data class OnZipFileSelected(val file: Uri) : DFUViewEvent()
+internal data class OnHexFileSelected(val file: Uri) : DFUViewEvent()
+internal data class OnDatFileSelected(val file: Uri) : DFUViewEvent()
internal object OnInstallButtonClick : DFUViewEvent()
diff --git a/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt b/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt
index 40cb5497..436abd1a 100644
--- a/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt
+++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/viewmodel/DFUViewModel.kt
@@ -1,32 +1,65 @@
package no.nordicsemi.dfu.viewmodel
-import android.net.Uri
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.combine
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
import no.nordicsemi.android.utils.exhaustive
+import no.nordicsemi.dfu.data.DFUFile
+import no.nordicsemi.dfu.data.DFUFileManager
+import no.nordicsemi.dfu.data.DFUManager
+import no.nordicsemi.dfu.data.DFUProgressManager
import no.nordicsemi.dfu.data.DFURepository
-import no.nordicsemi.dfu.view.*
-import java.io.File
+import no.nordicsemi.dfu.data.FileInstallingState
+import no.nordicsemi.dfu.data.FileReadyState
+import no.nordicsemi.dfu.view.DFUViewEvent
+import no.nordicsemi.dfu.view.OnDatFileSelected
+import no.nordicsemi.dfu.view.OnDisconnectButtonClick
+import no.nordicsemi.dfu.view.OnHexFileSelected
+import no.nordicsemi.dfu.view.OnInstallButtonClick
+import no.nordicsemi.dfu.view.OnPauseButtonClick
+import no.nordicsemi.dfu.view.OnStopButtonClick
+import no.nordicsemi.dfu.view.OnZipFileSelected
import javax.inject.Inject
@HiltViewModel
internal class DFUViewModel @Inject constructor(
private val repository: DFURepository,
+ private val progressManager: DFUProgressManager,
+ private val dfuManager: DFUManager,
+ private val fileManger: DFUFileManager
) : CloseableViewModel() {
- val state = repository.data
+ val state = repository.data.combine(progressManager.status) { state, status ->
+ (state as? FileInstallingState)?.run {
+ state.copy(status = status)
+ } ?: state
+ }
+
+ init {
+ progressManager.registerListener()
+ }
fun onEvent(event: DFUViewEvent) {
when (event) {
OnDisconnectButtonClick -> finish()
- is OnFileSelected -> repository.initFile(createFile(event.uri))
- OnInstallButtonClick -> repository.install()
+ OnInstallButtonClick -> {
+ dfuManager.install(requireFile())
+ repository.install()
+ }
OnPauseButtonClick -> finish()
OnStopButtonClick -> finish()
+ is OnHexFileSelected -> repository.
+ is OnZipFileSelected -> TODO()
+ is OnDatFileSelected -> TODO()
}.exhaustive
}
- private fun createFile(uri: Uri): File {
- return File(requireNotNull(uri.path))
+ private fun requireFile(): DFUFile {
+ return (repository.data.value as FileReadyState).file
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ progressManager.unregisterListener()
}
}
diff --git a/profile_dfu/src/main/res/values/strings.xml b/profile_dfu/src/main/res/values/strings.xml
index fde0470b..8dc227e9 100644
--- a/profile_dfu/src/main/res/values/strings.xml
+++ b/profile_dfu/src/main/res/values/strings.xml
@@ -8,6 +8,7 @@
Stop
Install
+ Select .dat
Select .zip
Select .hex
@@ -21,6 +22,7 @@
Choose file
Please select .zip or .hex file with bootloader, application or soft device.
+ Please select .dat file.
File managers
Please select
@@ -29,4 +31,19 @@
Operation success
Operation failed
+
+ Aborted
+ Completed
+ Connected
+ Connecting
+ Disconnected
+ Disconnecting
+ Enabling DFU
+ Error
+ Idle
+ %d%
+ Started
+ Starting
+ Validating
+ Status: %s