diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt index 3468f08b..768821fe 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt @@ -5,12 +5,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel import no.nordicsemi.android.navigation.ForwardDestination import no.nordicsemi.android.navigation.NavigationManager import no.nordicsemi.android.navigation.UUIDArgument -import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - private val deviceHolder: SelectedBluetoothDeviceHolder, private val navigationManager: NavigationManager ) : ViewModel() { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt index 7ee304cd..8bd5e425 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt @@ -24,9 +24,7 @@ class MainActivity : NordicActivity() { color = MaterialTheme.colorScheme.surface, modifier = Modifier.fillMaxSize() ) { - NavigationView( - HomeDestinations + ProfileDestinations + ScannerDestinations - ) + NavigationView(HomeDestinations + ProfileDestinations + ScannerDestinations) } } } diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt index fc69b65a..d2755791 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt @@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.asStateFlow import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.log.ILogSession import no.nordicsemi.android.log.Logger -import javax.inject.Inject @AndroidEntryPoint abstract class BleProfileService : Service() { @@ -47,9 +46,6 @@ abstract class BleProfileService : Service() { private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) val status = _status.asStateFlow() - @Inject - lateinit var bluetoothDeviceHolder: SelectedBluetoothDeviceHolder - /** * Returns a handler that is created in onCreate(). * The handler may be used to postpone execution of some operations or to run them in UI thread. @@ -58,17 +54,6 @@ abstract class BleProfileService : Service() { private var activityIsChangingConfiguration = false - /** - * Returns the Bluetooth device object - * - * @return bluetooth device - */ - private val bluetoothDevice: BluetoothDevice by lazy { - bluetoothDeviceHolder.device?.device ?: throw IllegalArgumentException( - "No device associated with the application." - ) - } - /** * Returns the log session that can be used to append log entries. The method returns `null` if the nRF Logger app was not installed. It is safe to use logger when * [.onServiceStarted] has been called. @@ -119,7 +104,7 @@ abstract class BleProfileService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - manager.connect(bluetoothDevice) + manager.connect(intent!!.getParcelableExtra(DEVICE_DATA)!!) .useAutoConnect(shouldAutoConnect()) .retry(3, 100) .enqueue() @@ -141,7 +126,6 @@ abstract class BleProfileService : Service() { // shutdown the manager manager.disconnect().enqueue() Logger.i(logSession, "Service destroyed") - bluetoothDeviceHolder.forgetDevice() logSession = null handler = null } @@ -184,14 +168,6 @@ abstract class BleProfileService : Service() { } } - /** - * Returns the device address - * - * @return device address - */ - protected val deviceAddress: String - get() = bluetoothDevice.address - /** * Returns `true` if the device is connected to the sensor. * diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt b/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt deleted file mode 100644 index 22518787..00000000 --- a/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt +++ /dev/null @@ -1,20 +0,0 @@ -package no.nordicsemi.android.service - -import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SelectedBluetoothDeviceHolder @Inject constructor() { - - var device: DiscoveredBluetoothDevice? = null - private set - - fun attachDevice(newDevice: DiscoveredBluetoothDevice) { - device = newDevice - } - - fun forgetDevice() { - device = null - } -} diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ServiceManager.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ServiceManager.kt new file mode 100644 index 00000000..f7b1e87b --- /dev/null +++ b/lib_service/src/main/java/no/nordicsemi/android/service/ServiceManager.kt @@ -0,0 +1,22 @@ +package no.nordicsemi.android.service + +import android.content.Context +import android.content.Intent +import dagger.hilt.android.qualifiers.ApplicationContext +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import javax.inject.Inject + +const val DEVICE_DATA = "device-data" + +class ServiceManager @Inject constructor( + @ApplicationContext + private val context: Context +) { + + fun startService(service: Class, device: DiscoveredBluetoothDevice) { + val intent = Intent(context, service).apply { + putExtra(DEVICE_DATA, device) + } + context.startService(intent) + } +} diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt index 741bd9dc..00a24842 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt @@ -16,20 +16,27 @@ import no.nordicsemi.android.bps.view.DisconnectEvent import no.nordicsemi.android.bps.view.DisplayDataState import no.nordicsemi.android.bps.view.LoadingState import no.nordicsemi.android.navigation.NavigationManager +import no.nordicsemi.android.navigation.ParcelableArgument +import no.nordicsemi.android.navigation.SuccessDestinationResult import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ConnectionObserverAdapter -import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @HiltViewModel internal class BPSViewModel @Inject constructor( private val bpsManager: BPSManager, - private val deviceHolder: SelectedBluetoothDeviceHolder, private val repository: BPSRepository, private val navigationManager: NavigationManager ) : ViewModel() { + private val args + get() = navigationManager.getResult(ScannerDestinationId) + private val device + get() = ((args as SuccessDestinationResult).argument as ParcelableArgument).value as DiscoveredBluetoothDevice + val state = repository.data.combine(repository.status) { data, status -> when (status) { BleManagerStatus.CONNECTING -> LoadingState @@ -70,7 +77,7 @@ internal class BPSViewModel @Inject constructor( } fun connectDevice() { - bpsManager.connect(deviceHolder.device!!.device) + bpsManager.connect(device.device) .useAutoConnect(false) .retry(3, 100) .enqueue() @@ -78,7 +85,6 @@ internal class BPSViewModel @Inject constructor( private fun onDisconnectButtonClick() { bpsManager.disconnect().enqueue() - deviceHolder.forgetDevice() repository.clear() } diff --git a/profile_csc/build.gradle b/profile_csc/build.gradle index 93ecd6f0..286b2e1c 100644 --- a/profile_csc/build.gradle +++ b/profile_csc/build.gradle @@ -11,6 +11,7 @@ dependencies { implementation libs.nordic.log implementation libs.nordic.theme implementation libs.nordic.navigation + implementation libs.nordic.ui.scanner implementation libs.bundles.compose implementation libs.androidx.core diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt index c3acf069..5ddc51cc 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt @@ -1,15 +1,11 @@ package no.nordicsemi.android.csc.view -import android.content.Intent import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.csc.R -import no.nordicsemi.android.csc.repository.CSCService import no.nordicsemi.android.csc.viewmodel.CSCViewModel import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.DeviceConnectingView @@ -20,24 +16,13 @@ fun CSCScreen() { val viewModel: CSCViewModel = hiltViewModel() val state = viewModel.state.collectAsState().value - val context = LocalContext.current - LaunchedEffect(Unit) { - val intent = Intent(context, CSCService::class.java) - context.startService(intent) - } - - CSCView(state) { viewModel.onEvent(it) } -} - -@Composable -private fun CSCView(state: CSCViewState, onEvent: (CSCViewEvent) -> Unit) { Column { BackIconAppBar(stringResource(id = R.string.csc_title)) { - onEvent(OnDisconnectButtonClick) + viewModel.onEvent(OnDisconnectButtonClick) } when (state) { - is DisplayDataState -> CSCContentView(state.data, onEvent) + is DisplayDataState -> CSCContentView(state.data) { viewModel.onEvent(it) } LoadingState -> DeviceConnectingView() }.exhaustive } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt index 4911ef9c..1aa68809 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt @@ -11,23 +11,37 @@ import kotlinx.coroutines.flow.stateIn import no.nordicsemi.android.csc.data.CSCRepository import no.nordicsemi.android.csc.data.DisconnectCommand import no.nordicsemi.android.csc.data.SetWheelSizeCommand +import no.nordicsemi.android.csc.repository.CSCService import no.nordicsemi.android.csc.view.CSCViewEvent import no.nordicsemi.android.csc.view.DisplayDataState import no.nordicsemi.android.csc.view.LoadingState import no.nordicsemi.android.csc.view.OnDisconnectButtonClick import no.nordicsemi.android.csc.view.OnSelectedSpeedUnitSelected import no.nordicsemi.android.csc.view.OnWheelSizeSelected +import no.nordicsemi.android.navigation.CancelDestinationResult +import no.nordicsemi.android.navigation.ForwardDestination import no.nordicsemi.android.navigation.NavigationManager +import no.nordicsemi.android.navigation.ParcelableArgument +import no.nordicsemi.android.navigation.SuccessDestinationResult import no.nordicsemi.android.service.BleManagerStatus +import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @HiltViewModel internal class CSCViewModel @Inject constructor( private val repository: CSCRepository, + private val serviceManager: ServiceManager, private val navigationManager: NavigationManager ) : ViewModel() { + private val args + get() = navigationManager.getResult(ScannerDestinationId) + private val device + get() = ((args as SuccessDestinationResult).argument as ParcelableArgument).value as DiscoveredBluetoothDevice + val state = repository.data.combine(repository.status) { data, status -> when (status) { BleManagerStatus.CONNECTING -> LoadingState @@ -37,6 +51,12 @@ internal class CSCViewModel @Inject constructor( }.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState) init { + when (args) { + CancelDestinationResult -> navigationManager.navigateUp() + is SuccessDestinationResult -> serviceManager.startService(CSCService::class.java, device) + null -> navigationManager.navigateTo(ForwardDestination(ScannerDestinationId)) + }.exhaustive + repository.status.onEach { if (it == BleManagerStatus.DISCONNECTED) { navigationManager.navigateUp() 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 index beb77178..988e2f44 100644 --- a/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUManager.kt +++ b/profile_dfu/src/main/java/no/nordicsemi/dfu/data/DFUManager.kt @@ -3,19 +3,16 @@ 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.DiscoveredBluetoothDevice import javax.inject.Inject class DFUManager @Inject constructor( @ApplicationContext - private val context: Context, - private val deviceHolder: SelectedBluetoothDeviceHolder + private val context: Context ) { - fun install(file: ZipFile) { - val device = deviceHolder.device!! - + fun install(file: ZipFile, device: DiscoveredBluetoothDevice) { val starter = DfuServiceInitiator(device.address()) .setDeviceName(device.displayName()) // .setKeepBond(keepBond) 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 493a4159..ad29b682 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 @@ -8,13 +8,12 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @Singleton internal class DFURepository @Inject constructor( - private val deviceHolder: SelectedBluetoothDeviceHolder, private val fileManger: DFUFileManager ) { @@ -27,10 +26,10 @@ internal class DFURepository @Inject constructor( private val _status = MutableStateFlow(BleManagerStatus.CONNECTING) val status = _status.asStateFlow() - fun setZipFile(file: Uri) { + fun setZipFile(file: Uri, device: DiscoveredBluetoothDevice) { val currentState = _data.value as NoFileSelectedState _data.value = fileManger.createFile(file)?.let { - FileReadyState(it, requireNotNull(deviceHolder.device)) + FileReadyState(it, device) } ?: currentState.copy(isError = true) } 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 db97ce78..72495ff8 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 @@ -6,8 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import no.nordicsemi.android.navigation.NavigationManager +import no.nordicsemi.android.navigation.ParcelableArgument +import no.nordicsemi.android.navigation.SuccessDestinationResult import no.nordicsemi.android.service.BleManagerStatus -import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.dfu.data.Completed import no.nordicsemi.dfu.data.DFUManager @@ -28,16 +30,24 @@ 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 no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @HiltViewModel internal class DFUViewModel @Inject constructor( private val repository: DFURepository, private val progressManager: DFUProgressManager, - private val deviceHolder: SelectedBluetoothDeviceHolder, - private val dfuManager: DFUManager + private val dfuManager: DFUManager, + private val navigationManager: NavigationManager ) : ViewModel() { + private val args + get() = navigationManager.getResult(ScannerDestinationId) + private val device + get() = ((args as SuccessDestinationResult).argument as ParcelableArgument).value as DiscoveredBluetoothDevice + + val state = repository.data.combine(progressManager.status) { state, status -> (state as? FileInstallingState) ?.run { createInstallingStateWithNewStatus(state, status) } @@ -58,19 +68,18 @@ internal class DFUViewModel @Inject constructor( when (event) { OnDisconnectButtonClick -> closeScreen() OnInstallButtonClick -> { - dfuManager.install(requireFile()) + dfuManager.install(requireFile(), device) repository.install() } OnPauseButtonClick -> closeScreen() OnStopButtonClick -> closeScreen() - is OnZipFileSelected -> repository.setZipFile(event.file) + is OnZipFileSelected -> repository.setZipFile(event.file, device) }.exhaustive } private fun closeScreen() { repository.sendNewCommand(DisconnectCommand) repository.clear() - deviceHolder.forgetDevice() } private fun requireFile(): ZipFile { diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt index 6e973eb7..9e8ac1bc 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt @@ -15,20 +15,27 @@ import no.nordicsemi.android.gls.repository.GLSManager import no.nordicsemi.android.gls.view.DisplayDataState import no.nordicsemi.android.gls.view.LoadingState import no.nordicsemi.android.navigation.NavigationManager +import no.nordicsemi.android.navigation.ParcelableArgument +import no.nordicsemi.android.navigation.SuccessDestinationResult import no.nordicsemi.android.service.BleManagerStatus import no.nordicsemi.android.service.ConnectionObserverAdapter -import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @HiltViewModel internal class GLSViewModel @Inject constructor( private val glsManager: GLSManager, - private val deviceHolder: SelectedBluetoothDeviceHolder, private val repository: GLSRepository, private val navigationManager: NavigationManager ) : ViewModel() { + private val args + get() = navigationManager.getResult(ScannerDestinationId) + private val device + get() = ((args as SuccessDestinationResult).argument as ParcelableArgument).value as DiscoveredBluetoothDevice + val state = repository.data.combine(repository.status) { data, status -> when (status) { BleManagerStatus.CONNECTING -> LoadingState @@ -70,12 +77,10 @@ internal class GLSViewModel @Inject constructor( } fun connectDevice() { - deviceHolder.device?.let { - glsManager.connect(it.device) - .useAutoConnect(false) - .retry(3, 100) - .enqueue() - } + glsManager.connect(device.device) + .useAutoConnect(false) + .retry(3, 100) + .enqueue() } private fun requestData(mode: WorkingMode) { @@ -87,7 +92,6 @@ internal class GLSViewModel @Inject constructor( } private fun disconnect() { - deviceHolder.forgetDevice() glsManager.disconnect().enqueue() }