From b0bf04bac2598ac514b2a54555f2f4551605a3b0 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Fri, 29 Sep 2023 10:29:29 +0200 Subject: [PATCH 1/3] Dependencies updated, old uiscanner moved to scanner module --- app/build.gradle.kts | 1 - .../nordicsemi/android/blinky/MainActivity.kt | 4 +- .../android/blinky/di/BlinkyModule.kt | 4 +- blinky/ble/build.gradle.kts | 2 +- .../android/blinky/ble/BlinkyManager.kt | 34 ++--- blinky/spec/build.gradle.kts | 2 +- blinky/ui/build.gradle.kts | 9 +- .../{ => ui}/control/BlinkyDestination.kt | 4 +- .../control/repository/BlinkyRepository.kt | 2 +- .../control/view/BlinkyControlView.kt | 2 +- .../{ => ui}/control/view/BlinkyScreen.kt | 14 +- .../control/view/ButtonControlView.kt | 4 +- .../{ => ui}/control/view/LedControlView.kt | 4 +- .../control/viewmodel/BlinkyViewModel.kt | 8 +- .../blinky/ui}/scanner/ScannerDestination.kt | 8 +- .../blinky/ui/scanner/view/BlinkyScanner.kt | 8 +- blinky/ui/src/main/res/values/string.xml | 2 + build.gradle.kts | 4 +- gradle/wrapper/gradle-wrapper.properties | 6 +- scanner/build.gradle.kts | 15 ++- .../android/scanner/ScannerScreen.kt | 70 ++++++++++ .../android/scanner/ScannerScreenResult.kt | 41 ++++++ .../nordicsemi/android/scanner/ScannerView.kt | 125 ++++++++++++++++++ .../android/scanner/main/DeviceListItem.kt | 107 +++++++++++++++ .../android/scanner/main/DeviceListItems.kt | 101 ++++++++++++++ .../android/scanner/main/DevicesListView.kt | 121 +++++++++++++++++ .../main/viewmodel/ScannerViewModel.kt | 101 ++++++++++++++ .../model/DiscoveredBluetoothDevice.kt | 112 ++++++++++++++++ .../scanner/repository/DevicesDataStore.kt | 65 +++++++++ .../scanner/repository/ScannerRepository.kt | 93 +++++++++++++ .../scanner/repository/ScanningState.kt | 55 ++++++++ .../scanner/view/DeviceConnectingView.kt | 110 +++++++++++++++ .../scanner/view/DeviceDisconnectedView.kt | 119 +++++++++++++++++ .../android/scanner/view/ScannerAppBar.kt | 68 ++++++++++ .../scanner/view/internal/FilterView.kt | 125 ++++++++++++++++++ .../scanner/view/internal/ScanEmptyView.kt | 106 +++++++++++++++ .../scanner/view/internal/ScanErrorView.kt | 66 +++++++++ scanner/src/main/res/values/strings.xml | 42 +++++- settings.gradle.kts | 8 +- 39 files changed, 1702 insertions(+), 70 deletions(-) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/BlinkyDestination.kt (86%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/repository/BlinkyRepository.kt (97%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/view/BlinkyControlView.kt (95%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/view/BlinkyScreen.kt (88%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/view/ButtonControlView.kt (96%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/view/LedControlView.kt (96%) rename blinky/ui/src/main/java/no/nordicsemi/android/blinky/{ => ui}/control/viewmodel/BlinkyViewModel.kt (91%) rename {scanner/src/main/java/no/nordicsemi/android/blinky => blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui}/scanner/ScannerDestination.kt (72%) rename scanner/src/main/java/no/nordicsemi/android/blinky/scanner/view/ScannerScreen.kt => blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/view/BlinkyScanner.kt (76%) create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreen.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreenResult.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/ScannerView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItem.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItems.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/main/DevicesListView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/main/viewmodel/ScannerViewModel.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/model/DiscoveredBluetoothDevice.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/repository/DevicesDataStore.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScannerRepository.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScanningState.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceConnectingView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceDisconnectedView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/ScannerAppBar.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/FilterView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanEmptyView.kt create mode 100644 scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanErrorView.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5f2eb287..4346b5d9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,6 @@ android { } dependencies { - implementation(project(":scanner")) implementation(project(":blinky:spec")) implementation(project(":blinky:ui")) implementation(project(":blinky:ble")) diff --git a/app/src/main/java/no/nordicsemi/android/blinky/MainActivity.kt b/app/src/main/java/no/nordicsemi/android/blinky/MainActivity.kt index 901df7dd..bbcdfb17 100644 --- a/app/src/main/java/no/nordicsemi/android/blinky/MainActivity.kt +++ b/app/src/main/java/no/nordicsemi/android/blinky/MainActivity.kt @@ -3,8 +3,8 @@ package no.nordicsemi.android.blinky import android.os.Bundle import androidx.activity.compose.setContent import dagger.hilt.android.AndroidEntryPoint -import no.nordicsemi.android.blinky.control.BlinkyDestination -import no.nordicsemi.android.blinky.scanner.ScannerDestination +import no.nordicsemi.android.blinky.ui.control.BlinkyDestination +import no.nordicsemi.android.blinky.ui.scanner.ScannerDestination import no.nordicsemi.android.common.navigation.NavigationView import no.nordicsemi.android.common.theme.NordicActivity import no.nordicsemi.android.common.theme.NordicTheme diff --git a/app/src/main/java/no/nordicsemi/android/blinky/di/BlinkyModule.kt b/app/src/main/java/no/nordicsemi/android/blinky/di/BlinkyModule.kt index 257bdc9e..e1bf6956 100644 --- a/app/src/main/java/no/nordicsemi/android/blinky/di/BlinkyModule.kt +++ b/app/src/main/java/no/nordicsemi/android/blinky/di/BlinkyModule.kt @@ -11,7 +11,7 @@ import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ViewModelScoped import no.nordicsemi.android.blinky.ble.BlinkyManager -import no.nordicsemi.android.blinky.control.Blinky +import no.nordicsemi.android.blinky.ui.control.Blinky import no.nordicsemi.android.blinky.spec.Blinky import no.nordicsemi.android.blinky.spec.R import no.nordicsemi.android.common.navigation.get @@ -58,7 +58,7 @@ abstract class BlinkyModule { @Binds abstract fun bindBlinky( - BlinkManager: BlinkyManager + blinkyManager: BlinkyManager ): Blinky } \ No newline at end of file diff --git a/blinky/ble/build.gradle.kts b/blinky/ble/build.gradle.kts index 56a58b1f..f7a8b546 100644 --- a/blinky/ble/build.gradle.kts +++ b/blinky/ble/build.gradle.kts @@ -1,7 +1,7 @@ plugins { // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt alias(libs.plugins.nordic.library) - // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidLKotlinConventionPlugin.kt + // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidKotlinConventionPlugin.kt alias(libs.plugins.nordic.kotlin) } diff --git a/blinky/ble/src/main/java/no/nordicsemi/android/blinky/ble/BlinkyManager.kt b/blinky/ble/src/main/java/no/nordicsemi/android/blinky/ble/BlinkyManager.kt index 35810c86..e63f6af0 100644 --- a/blinky/ble/src/main/java/no/nordicsemi/android/blinky/ble/BlinkyManager.kt +++ b/blinky/ble/src/main/java/no/nordicsemi/android/blinky/ble/BlinkyManager.kt @@ -53,6 +53,23 @@ private class BlinkyManagerImpl( } .stateIn(scope, SharingStarted.Lazily, Blinky.State.NOT_AVAILABLE) + + private val buttonCallback by lazy { + object : ButtonCallback() { + override fun onButtonStateChanged(device: BluetoothDevice, state: Boolean) { + _buttonState.tryEmit(state) + } + } + } + + private val ledCallback by lazy { + object : LedCallback() { + override fun onLedStateChanged(device: BluetoothDevice, state: Boolean) { + _ledState.tryEmit(state) + } + } + } + override suspend fun connect() = connect(device) .retry(3, 300) .useAutoConnect(false) @@ -96,22 +113,6 @@ private class BlinkyManagerImpl( return Log.VERBOSE } - private val buttonCallback by lazy { - object : ButtonCallback() { - override fun onButtonStateChanged(device: BluetoothDevice, state: Boolean) { - _buttonState.tryEmit(state) - } - } - } - - private val ledCallback by lazy { - object : LedCallback() { - override fun onLedStateChanged(device: BluetoothDevice, state: Boolean) { - _ledState.tryEmit(state) - } - } - } - override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean { // Get the LBS Service from the gatt object. gatt.getService(BlinkySpec.BLINKY_SERVICE_UUID)?.apply { @@ -164,5 +165,4 @@ private class BlinkyManagerImpl( ledCharacteristic = null buttonCharacteristic = null } - } \ No newline at end of file diff --git a/blinky/spec/build.gradle.kts b/blinky/spec/build.gradle.kts index 47bc3abc..ef8b304c 100644 --- a/blinky/spec/build.gradle.kts +++ b/blinky/spec/build.gradle.kts @@ -1,7 +1,7 @@ plugins { // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt alias(libs.plugins.nordic.library) - // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidLKotlinConventionPlugin.kt + // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidKotlinConventionPlugin.kt alias(libs.plugins.nordic.kotlin) } diff --git a/blinky/ui/build.gradle.kts b/blinky/ui/build.gradle.kts index ba1a5579..299efb95 100644 --- a/blinky/ui/build.gradle.kts +++ b/blinky/ui/build.gradle.kts @@ -2,21 +2,22 @@ plugins { // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidFeatureConventionPlugin.kt alias(libs.plugins.nordic.feature) // https://developer.android.com/kotlin/parcelize - id("kotlin-parcelize") + alias(libs.plugins.kotlin.parcelize) } android { - namespace = "no.nordicsemi.android.blinky.control" + namespace = "no.nordicsemi.android.blinky.ui" } dependencies { implementation(project(":blinky:spec")) + implementation(project(":scanner")) implementation(libs.nordic.theme) + implementation(libs.nordic.logger) implementation(libs.nordic.uilogger) - implementation(libs.nordic.uiscanner) implementation(libs.nordic.navigation) - implementation(libs.nordic.permission) + implementation(libs.nordic.permissions.ble) implementation(libs.nordic.log.timber) implementation(libs.androidx.compose.material.iconsExtended) diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/BlinkyDestination.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/BlinkyDestination.kt similarity index 86% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/BlinkyDestination.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/BlinkyDestination.kt index b4b9e27a..0fe2446f 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/BlinkyDestination.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/BlinkyDestination.kt @@ -1,10 +1,10 @@ -package no.nordicsemi.android.blinky.control +package no.nordicsemi.android.blinky.ui.control import android.bluetooth.BluetoothDevice import android.os.Parcelable import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.parcelize.Parcelize -import no.nordicsemi.android.blinky.control.view.BlinkyScreen +import no.nordicsemi.android.blinky.ui.control.view.BlinkyScreen import no.nordicsemi.android.common.navigation.createDestination import no.nordicsemi.android.common.navigation.defineDestination import no.nordicsemi.android.common.navigation.viewmodel.SimpleNavigationViewModel diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/repository/BlinkyRepository.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/repository/BlinkyRepository.kt similarity index 97% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/repository/BlinkyRepository.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/repository/BlinkyRepository.kt index 284a5b3c..cf68f8e2 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/repository/BlinkyRepository.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/repository/BlinkyRepository.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.repository +package no.nordicsemi.android.blinky.ui.control.repository import android.content.Context import android.net.Uri diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyControlView.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyControlView.kt similarity index 95% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyControlView.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyControlView.kt index 436d4d6d..9f12edcd 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyControlView.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyControlView.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.view +package no.nordicsemi.android.blinky.ui.control.view import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyScreen.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyScreen.kt similarity index 88% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyScreen.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyScreen.kt index 674070be..52149dae 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/BlinkyScreen.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/BlinkyScreen.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.view +package no.nordicsemi.android.blinky.ui.control.view import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -16,15 +16,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import no.nordicsemi.android.blinky.control.R -import no.nordicsemi.android.blinky.control.viewmodel.BlinkyViewModel +import no.nordicsemi.android.blinky.ui.R +import no.nordicsemi.android.blinky.ui.control.viewmodel.BlinkyViewModel import no.nordicsemi.android.blinky.spec.Blinky +import no.nordicsemi.android.scanner.view.DeviceConnectingView +import no.nordicsemi.android.scanner.view.DeviceDisconnectedView +import no.nordicsemi.android.scanner.view.Reason import no.nordicsemi.android.common.logger.view.LoggerAppBarIcon -import no.nordicsemi.android.common.permission.RequireBluetooth +import no.nordicsemi.android.common.permissions.ble.RequireBluetooth import no.nordicsemi.android.common.theme.view.NordicAppBar -import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView -import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView -import no.nordicsemi.android.common.ui.scanner.view.Reason @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/ButtonControlView.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/ButtonControlView.kt similarity index 96% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/ButtonControlView.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/ButtonControlView.kt index e5b95fc2..d301d395 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/ButtonControlView.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/ButtonControlView.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.view +package no.nordicsemi.android.blinky.ui.control.view import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -17,7 +17,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import no.nordicsemi.android.blinky.control.R +import no.nordicsemi.android.blinky.ui.R import no.nordicsemi.android.common.theme.NordicTheme @Composable diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/LedControlView.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/LedControlView.kt similarity index 96% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/LedControlView.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/LedControlView.kt index 82ee2ee3..508f56e0 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/view/LedControlView.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/view/LedControlView.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.view +package no.nordicsemi.android.blinky.ui.control.view import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -16,7 +16,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import no.nordicsemi.android.blinky.control.R +import no.nordicsemi.android.blinky.ui.R import no.nordicsemi.android.common.theme.NordicTheme @Composable diff --git a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/viewmodel/BlinkyViewModel.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/viewmodel/BlinkyViewModel.kt similarity index 91% rename from blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/viewmodel/BlinkyViewModel.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/viewmodel/BlinkyViewModel.kt index 839b9e0a..2163249c 100644 --- a/blinky/ui/src/main/java/no/nordicsemi/android/blinky/control/viewmodel/BlinkyViewModel.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/control/viewmodel/BlinkyViewModel.kt @@ -1,4 +1,4 @@ -package no.nordicsemi.android.blinky.control.viewmodel +package no.nordicsemi.android.blinky.ui.control.viewmodel import android.app.Application import android.content.Context @@ -11,8 +11,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import no.nordicsemi.android.blinky.control.repository.BlinkyRepository -import no.nordicsemi.android.common.logger.NordicLogger +import no.nordicsemi.android.blinky.ui.control.repository.BlinkyRepository +import no.nordicsemi.android.common.logger.LoggerLauncher import javax.inject.Inject import javax.inject.Named @@ -73,7 +73,7 @@ class BlinkyViewModel @Inject constructor( * Opens nRF Logger app with the log or Google Play if the app is not installed. */ fun openLogger() { - NordicLogger.launch(getApplication(), repository.sessionUri) + LoggerLauncher.launch(getApplication(), repository.sessionUri) } override fun onCleared() { diff --git a/scanner/src/main/java/no/nordicsemi/android/blinky/scanner/ScannerDestination.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/ScannerDestination.kt similarity index 72% rename from scanner/src/main/java/no/nordicsemi/android/blinky/scanner/ScannerDestination.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/ScannerDestination.kt index 6dd805e2..709a940b 100644 --- a/scanner/src/main/java/no/nordicsemi/android/blinky/scanner/ScannerDestination.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/ScannerDestination.kt @@ -1,9 +1,9 @@ -package no.nordicsemi.android.blinky.scanner +package no.nordicsemi.android.blinky.ui.scanner import androidx.hilt.navigation.compose.hiltViewModel -import no.nordicsemi.android.blinky.control.Blinky -import no.nordicsemi.android.blinky.control.BlinkyDevice -import no.nordicsemi.android.blinky.scanner.view.BlinkyScanner +import no.nordicsemi.android.blinky.ui.control.Blinky +import no.nordicsemi.android.blinky.ui.control.BlinkyDevice +import no.nordicsemi.android.blinky.ui.scanner.view.BlinkyScanner import no.nordicsemi.android.common.navigation.createSimpleDestination import no.nordicsemi.android.common.navigation.defineDestination import no.nordicsemi.android.common.navigation.viewmodel.SimpleNavigationViewModel diff --git a/scanner/src/main/java/no/nordicsemi/android/blinky/scanner/view/ScannerScreen.kt b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/view/BlinkyScanner.kt similarity index 76% rename from scanner/src/main/java/no/nordicsemi/android/blinky/scanner/view/ScannerScreen.kt rename to blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/view/BlinkyScanner.kt index 387b684c..e4d07d56 100644 --- a/scanner/src/main/java/no/nordicsemi/android/blinky/scanner/view/ScannerScreen.kt +++ b/blinky/ui/src/main/java/no/nordicsemi/android/blinky/ui/scanner/view/BlinkyScanner.kt @@ -1,13 +1,13 @@ -package no.nordicsemi.android.blinky.scanner.view +package no.nordicsemi.android.blinky.ui.scanner.view import android.bluetooth.BluetoothDevice import android.os.ParcelUuid import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import no.nordicsemi.android.blinky.scanner.R +import no.nordicsemi.android.blinky.ui.R +import no.nordicsemi.android.scanner.DeviceSelected +import no.nordicsemi.android.scanner.ScannerScreen import no.nordicsemi.android.blinky.spec.BlinkySpec -import no.nordicsemi.android.common.ui.scanner.DeviceSelected -import no.nordicsemi.android.common.ui.scanner.ScannerScreen @Composable fun BlinkyScanner( diff --git a/blinky/ui/src/main/res/values/string.xml b/blinky/ui/src/main/res/values/string.xml index da0787b8..1f228411 100644 --- a/blinky/ui/src/main/res/values/string.xml +++ b/blinky/ui/src/main/res/values/string.xml @@ -10,4 +10,6 @@ The button state ON OFF + + nRF Blinky \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1e09ee57..ff49f38b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,14 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.kapt) apply false + alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.hilt) apply false // Nordic plugins are defined in https://github.com/NordicSemiconductor/Android-Gradle-Plugins - alias(libs.plugins.nordic.application) apply false alias(libs.plugins.nordic.application.compose) apply false alias(libs.plugins.nordic.library) apply false - alias(libs.plugins.nordic.library.compose) apply false alias(libs.plugins.nordic.feature) apply false alias(libs.plugins.nordic.kotlin) apply false alias(libs.plugins.nordic.hilt) apply false + alias(libs.plugins.nordic.kotlin) apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 62ed5e83..14095d89 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Sep 21 12:47:34 CEST 2022 +#Thu Sep 28 13:02:20 CEST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/scanner/build.gradle.kts b/scanner/build.gradle.kts index 7f8848e0..533592b4 100644 --- a/scanner/build.gradle.kts +++ b/scanner/build.gradle.kts @@ -1,16 +1,21 @@ plugins { // https://github.com/NordicSemiconductor/Android-Gradle-Plugins/blob/main/plugins/src/main/kotlin/AndroidFeatureConventionPlugin.kt alias(libs.plugins.nordic.feature) + // https://developer.android.com/kotlin/parcelize + alias(libs.plugins.kotlin.parcelize) } android { - namespace = "no.nordicsemi.android.blinky.scanner" + namespace = "no.nordicsemi.android.scanner" } dependencies { - implementation(project(":blinky:spec")) - implementation(project(":blinky:ui")) - - implementation(libs.nordic.uiscanner) + implementation(libs.nordic.core) + implementation(libs.nordic.theme) + implementation(libs.nordic.scanner) implementation(libs.nordic.navigation) + implementation(libs.nordic.permissions.ble) + + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material.iconsExtended) } \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreen.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreen.kt new file mode 100644 index 00000000..30fca123 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreen.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.scanner + +import android.os.ParcelUuid +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import no.nordicsemi.android.scanner.main.DeviceListItem +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice +import no.nordicsemi.android.scanner.view.ScannerAppBar + +@Composable +fun ScannerScreen( + title: String = stringResource(id = R.string.scanner_screen), + uuid: ParcelUuid?, + cancellable: Boolean = true, + onResult: (ScannerScreenResult) -> Unit, + deviceItem: @Composable (DiscoveredBluetoothDevice) -> Unit = { + DeviceListItem(it.displayName, it.address) + } +) { + var isScanning by rememberSaveable { mutableStateOf(false) } + + Column { + if (cancellable) { + ScannerAppBar(title, isScanning) { onResult(ScanningCancelled) } + } else { + ScannerAppBar(title, isScanning) + } + ScannerView( + uuid = uuid, + onScanningStateChanged = { isScanning = it }, + onResult = { onResult(DeviceSelected(it)) }, + deviceItem = deviceItem, + ) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreenResult.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreenResult.kt new file mode 100644 index 00000000..0075deb6 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerScreenResult.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner + +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice + + +sealed interface ScannerScreenResult + +data object ScanningCancelled : ScannerScreenResult + +data class DeviceSelected(val device: DiscoveredBluetoothDevice) : ScannerScreenResult \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerView.kt new file mode 100644 index 00000000..2404b2a2 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/ScannerView.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner + +import android.os.ParcelUuid +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import no.nordicsemi.android.scanner.main.DeviceListItem +import no.nordicsemi.android.scanner.main.DevicesListView +import no.nordicsemi.android.scanner.main.viewmodel.ScannerViewModel +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice +import no.nordicsemi.android.scanner.repository.ScanningState +import no.nordicsemi.android.scanner.view.internal.FilterView +import no.nordicsemi.android.common.permissions.ble.RequireBluetooth +import no.nordicsemi.android.common.permissions.ble.RequireLocation +import no.nordicsemi.android.common.theme.R + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ScannerView( + uuid: ParcelUuid?, + onScanningStateChanged: (Boolean) -> Unit = {}, + onResult: (DiscoveredBluetoothDevice) -> Unit, + deviceItem: @Composable (DiscoveredBluetoothDevice) -> Unit = { + DeviceListItem(it.displayName, it.address) + } +) { + RequireBluetooth( + onChanged = { onScanningStateChanged(it) } + ) { + RequireLocation( + onChanged = { onScanningStateChanged(it) } + ) { isLocationRequiredAndDisabled -> + val viewModel = hiltViewModel() + .apply { setFilterUuid(uuid) } + + val state by viewModel.state.collectAsStateWithLifecycle(ScanningState.Loading) + val config by viewModel.filterConfig.collectAsStateWithLifecycle() + var refreshing by remember { mutableStateOf(false) } + + val scope = rememberCoroutineScope() + fun refresh() = scope.launch { + refreshing = true + viewModel.refresh() + delay(400) // TODO remove this delay and refreshing variable after updating material dependency + refreshing = false + } + + Column(modifier = Modifier.fillMaxSize()) { + FilterView( + config = config, + onChanged = { viewModel.setFilter(it) }, + modifier = Modifier + .fillMaxWidth() + .background(colorResource(id = R.color.appBarColor)) + .padding(horizontal = 16.dp), + ) + + val pullRefreshState = rememberPullRefreshState( + refreshing = refreshing, + onRefresh = { refresh() }, + ) + + Box(modifier = Modifier.pullRefresh(pullRefreshState).clipToBounds()) { + DevicesListView( + isLocationRequiredAndDisabled = isLocationRequiredAndDisabled, + state = state, + modifier = Modifier.fillMaxSize(), + onClick = { onResult(it) }, + deviceItem = deviceItem, + ) + + PullRefreshIndicator( + refreshing = refreshing, + state = pullRefreshState, + Modifier.align(Alignment.TopCenter) + ) + } + } + } + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItem.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItem.kt new file mode 100644 index 00000000..a51a1150 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItem.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.main + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Bluetooth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.common.theme.view.CircularIcon +import no.nordicsemi.android.common.theme.view.RssiIcon + +@Composable +internal fun DeviceListItem( + name: String?, + address: String, + modifier: Modifier = Modifier, + extras: @Composable () -> Unit = {}, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically) + { + CircularIcon(Icons.Default.Bluetooth) + + Spacer(modifier = Modifier.width(16.dp)) + + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + name?.let { name -> + Text( + text = name, + style = MaterialTheme.typography.titleMedium + ) + } ?: Text( + text = stringResource(id = R.string.device_no_name), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.alpha(0.7f) + ) + Text( + text = address, + style = MaterialTheme.typography.bodyMedium + ) + } + + extras() + } +} + +@Preview +@Composable +private fun DeviceListItemPreview() { + NordicTheme { + DeviceListItem( + name = "Device name", + address = "AA:BB:CC:DD:EE:FF", + extras = { + RssiIcon(rssi = -45) + } + ) + } +} diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItems.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItems.kt new file mode 100644 index 00000000..a736dfc8 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DeviceListItems.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.main + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice +import no.nordicsemi.android.scanner.repository.ScanningState + +@Suppress("FunctionName") +internal fun LazyListScope.DeviceListItems( + devices: ScanningState.DevicesDiscovered, + onClick: (DiscoveredBluetoothDevice) -> Unit, + deviceView: @Composable (DiscoveredBluetoothDevice) -> Unit, +) { + val bondedDevices = devices.bonded + val discoveredDevices = devices.notBonded + + if (bondedDevices.isNotEmpty()) { + item { + Text( + text = stringResource(id = R.string.bonded_devices), + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.padding(start = 8.dp, bottom = 8.dp) + ) + } + items(bondedDevices) { device -> + ClickableDeviceItem(device, onClick, deviceView) + } + } + + if (discoveredDevices.isNotEmpty()) { + item { + Text( + text = stringResource(id = R.string.discovered_devices), + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.padding(start = 8.dp, bottom = 8.dp) + ) + } + + items(discoveredDevices) { device -> + ClickableDeviceItem(device, onClick, deviceView) + } + } +} + +@Composable +private fun ClickableDeviceItem( + device: DiscoveredBluetoothDevice, + onClick: (DiscoveredBluetoothDevice) -> Unit, + deviceView: @Composable (DiscoveredBluetoothDevice) -> Unit, +) { + Box(modifier = Modifier + .clip(RoundedCornerShape(10.dp)) + .clickable { onClick(device) } + .padding(8.dp) + ) { + deviceView(device) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/main/DevicesListView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DevicesListView.kt new file mode 100644 index 00000000..13007872 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/main/DevicesListView.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.main + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice +import no.nordicsemi.android.scanner.repository.ScanningState +import no.nordicsemi.android.scanner.view.internal.ScanEmptyView +import no.nordicsemi.android.scanner.view.internal.ScanErrorView +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.support.v18.scanner.ScanCallback + +@Composable +internal fun DevicesListView( + isLocationRequiredAndDisabled: Boolean, + state: ScanningState, + onClick: (DiscoveredBluetoothDevice) -> Unit, + modifier: Modifier = Modifier, + deviceItem: @Composable (DiscoveredBluetoothDevice) -> Unit = { + DeviceListItem(it.displayName, it.address) + }, +) { + LazyColumn( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 16.dp) + ) { + when (state) { + is ScanningState.Loading -> item { ScanEmptyView(isLocationRequiredAndDisabled) } + is ScanningState.DevicesDiscovered -> { + if (state.isEmpty()) { + item { ScanEmptyView(isLocationRequiredAndDisabled) } + } else { + DeviceListItems(state, onClick, deviceItem) + } + } + is ScanningState.Error -> item { ScanErrorView(state.errorCode) } + } + } +} + +@Preview(name = "Location required") +@Composable +private fun DeviceListView_Preview_LocationRequired() { + NordicTheme { + DevicesListView( + isLocationRequiredAndDisabled = true, + state = ScanningState.Loading, + onClick = {} + ) + } +} + +@Preview +@Composable +private fun DeviceListView_Preview_LocationNotRequired() { + NordicTheme { + DevicesListView( + isLocationRequiredAndDisabled = false, + state = ScanningState.Loading, + onClick = {} + ) + } +} + +@Preview +@Composable +private fun DeviceListView_Preview_Error() { + NordicTheme { + DevicesListView( + isLocationRequiredAndDisabled = true, + state = ScanningState.Error(ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED), + onClick = {} + ) + } +} + +@Preview +@Composable +private fun DeviceListView_Preview_Empty() { + NordicTheme { + DevicesListView( + isLocationRequiredAndDisabled = true, + state = ScanningState.DevicesDiscovered(emptyList()), + onClick = {} + ) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/main/viewmodel/ScannerViewModel.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/main/viewmodel/ScannerViewModel.kt new file mode 100644 index 00000000..062b3f57 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/main/viewmodel/ScannerViewModel.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.main.viewmodel + +import android.os.ParcelUuid +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import no.nordicsemi.android.scanner.repository.DevicesScanFilter +import no.nordicsemi.android.scanner.repository.ScannerRepository +import no.nordicsemi.android.scanner.repository.ScanningState +import javax.inject.Inject + +private const val FILTER_RSSI = -50 // [dBm] + +@HiltViewModel +internal class ScannerViewModel @Inject constructor( + private val scannerRepository: ScannerRepository, +) : ViewModel() { + private var uuid: ParcelUuid? = null + + val filterConfig = MutableStateFlow( + DevicesScanFilter( + filterUuidRequired = true, + filterNearbyOnly = false, + filterWithNames = true + ) + ) + + val state = filterConfig + .combine(scannerRepository.getScannerState()) { config, result -> + when (result) { + is ScanningState.DevicesDiscovered -> result.applyFilters(config) + else -> result + } + } + // This can't be observed in View Model Scope, as it can exist even when the + // scanner is not visible. Scanner state stops scanning when it is not observed. + // .stateIn(viewModelScope, SharingStarted.Lazily, ScanningState.Loading) + + private fun ScanningState.DevicesDiscovered.applyFilters(config: DevicesScanFilter) = + ScanningState.DevicesDiscovered(devices + .filter { + uuid == null || + config.filterUuidRequired == false || + it.scanResult?.scanRecord?.serviceUuids?.contains(uuid) == true + } + .filter { !config.filterNearbyOnly || it.highestRssi >= FILTER_RSSI } + .filter { !config.filterWithNames || it.hadName } + ) + + fun setFilterUuid(uuid: ParcelUuid?) { + this.uuid = uuid + if (uuid == null) { + filterConfig.value = filterConfig.value.copy(filterUuidRequired = null) + } + } + + fun setFilter(config: DevicesScanFilter) { + this.filterConfig.value = config + } + + fun refresh() { + scannerRepository.clear() + } + + override fun onCleared() { + super.onCleared() + scannerRepository.clear() + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/model/DiscoveredBluetoothDevice.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/model/DiscoveredBluetoothDevice.kt new file mode 100644 index 00000000..79163612 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/model/DiscoveredBluetoothDevice.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.model + +import android.bluetooth.BluetoothDevice +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import no.nordicsemi.android.support.v18.scanner.ScanResult +import java.lang.Integer.max + +@Suppress("unused") +@Parcelize +data class DiscoveredBluetoothDevice( + val device: BluetoothDevice, + val scanResult: ScanResult? = null, + val name: String? = null, + val hadName: Boolean = name != null, + val lastScanResult: ScanResult? = null, + val rssi: Int = 0, + val previousRssi: Int = 0, + val highestRssi: Int = max(rssi, previousRssi), +) : Parcelable { + + fun hasRssiLevelChanged(): Boolean { + val newLevel = + if (rssi <= 10) 0 else if (rssi <= 28) 1 else if (rssi <= 45) 2 else if (rssi <= 65) 3 else 4 + val oldLevel = + if (previousRssi <= 10) 0 else if (previousRssi <= 28) 1 else if (previousRssi <= 45) 2 else if (previousRssi <= 65) 3 else 4 + return newLevel != oldLevel + } + + fun update(scanResult: ScanResult): DiscoveredBluetoothDevice = copy( + device = scanResult.device, + lastScanResult = scanResult, + name = scanResult.scanRecord?.deviceName, + hadName = hadName || name != null, + previousRssi = rssi, + rssi = scanResult.rssi, + highestRssi = if (highestRssi > rssi) highestRssi else rssi + ) + + fun matches(scanResult: ScanResult) = device.address == scanResult.device.address + + fun createBond() { + device.createBond() + } + + val displayName: String? + get() = when { + name?.isNotEmpty() == true -> name + device.name?.isNotEmpty() == true -> device.name + else -> null + } + + val address: String + get() = device.address + + val displayNameOrAddress: String + get() = displayName ?: address + + val bondingState: Int + get() = device.bondState + + val isBonded: Boolean + get() = bondingState == BluetoothDevice.BOND_BONDED + + override fun hashCode() = device.hashCode() + + override fun equals(other: Any?): Boolean { + if (other is DiscoveredBluetoothDevice) { + return device == other.device + } + return super.equals(other) + } +} + +fun ScanResult.toDiscoveredBluetoothDevice() = DiscoveredBluetoothDevice( + device = device, + scanResult = this, + name = scanRecord?.deviceName, + previousRssi = rssi, + rssi = rssi +) \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/repository/DevicesDataStore.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/DevicesDataStore.kt new file mode 100644 index 00000000..cdcc33f0 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/DevicesDataStore.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice +import no.nordicsemi.android.scanner.model.toDiscoveredBluetoothDevice +import no.nordicsemi.android.support.v18.scanner.ScanResult +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class DevicesDataStore @Inject constructor() { + val devices = mutableListOf() + val data = MutableStateFlow(devices.toList()) + + fun addNewDevice(scanResult: ScanResult) { + devices.firstOrNull { it.device == scanResult.device }?.let { device -> + val i = devices.indexOf(device) + devices.set(i, device.update(scanResult)) + } ?: scanResult.toDiscoveredBluetoothDevice().also { devices.add(it) } + + data.value = devices.toList() + } + + fun clear() { + devices.clear() + data.value = devices + } +} + +internal data class DevicesScanFilter( + val filterUuidRequired: Boolean?, + val filterNearbyOnly: Boolean, + val filterWithNames: Boolean +) \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScannerRepository.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScannerRepository.kt new file mode 100644 index 00000000..3ed4f436 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScannerRepository.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.repository + +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat +import no.nordicsemi.android.support.v18.scanner.ScanCallback +import no.nordicsemi.android.support.v18.scanner.ScanResult +import no.nordicsemi.android.support.v18.scanner.ScanSettings +import javax.inject.Inject + +@ViewModelScoped +internal class ScannerRepository @Inject internal constructor( + private val devicesDataStore: DevicesDataStore +) { + fun getScannerState(): Flow = + callbackFlow { + val scanCallback: ScanCallback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + if (result.isConnectable) { + devicesDataStore.addNewDevice(result) + + trySend(ScanningState.DevicesDiscovered(devicesDataStore.devices)) + } + } + + override fun onBatchScanResults(results: List) { + val newResults = results.filter { it.isConnectable } + newResults.forEach { + devicesDataStore.addNewDevice(it) + } + if (newResults.isNotEmpty()) { + trySend(ScanningState.DevicesDiscovered(devicesDataStore.devices)) + } + } + + override fun onScanFailed(errorCode: Int) { + trySend(ScanningState.Error(errorCode)) + } + } + + trySend(ScanningState.Loading) + + val settings = ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setLegacy(false) + .setReportDelay(500) + .setUseHardwareBatchingIfSupported(false) + .build() + val scanner = BluetoothLeScannerCompat.getScanner() + scanner.startScan(null, settings, scanCallback) + + awaitClose { + scanner.stopScan(scanCallback) + } + } + + fun clear() { + devicesDataStore.clear() + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScanningState.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScanningState.kt new file mode 100644 index 00000000..64ca1537 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/repository/ScanningState.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.repository + +import no.nordicsemi.android.scanner.model.DiscoveredBluetoothDevice + +internal sealed class ScanningState { + + data object Loading : ScanningState() + + data class Error(val errorCode: Int) : ScanningState() + + data class DevicesDiscovered(val devices: List) : ScanningState() { + val bonded: List = devices.filter { it.isBonded } + + val notBonded: List = devices.filter { !it.isBonded } + + fun size(): Int = bonded.size + notBonded.size + + fun isEmpty(): Boolean = devices.isEmpty() + } + + fun isRunning(): Boolean { + return this is Loading || this is DevicesDiscovered + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceConnectingView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceConnectingView.kt new file mode 100644 index 00000000..ea5beead --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceConnectingView.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HourglassTop +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.common.theme.view.CircularIcon + +@Composable +fun DeviceConnectingView( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.(PaddingValues) -> Unit = {} +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedCard( + modifier = Modifier + .widthIn(max = 460.dp), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircularIcon(imageVector = Icons.Default.HourglassTop) + + Text( + text = stringResource(id = R.string.device_connecting), + style = MaterialTheme.typography.titleMedium + ) + + Text( + text = stringResource(id = R.string.device_explanation), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + + Text( + text = stringResource(id = R.string.device_please_wait), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge + ) + } + } + + content(PaddingValues(top = 16.dp)) + } +} + +@Preview +@Composable +private fun DeviceConnectingView_Preview() { + NordicTheme { + DeviceConnectingView { padding -> + Button( + onClick = {}, + modifier = Modifier.padding(padding) + ) { + Text(text = "Cancel") + } + } + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceDisconnectedView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceDisconnectedView.kt new file mode 100644 index 00000000..de7f5bfe --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/DeviceDisconnectedView.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HighlightOff +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.common.theme.view.CircularIcon + +enum class Reason { + USER, UNKNOWN, LINK_LOSS, MISSING_SERVICE +} + +@Composable +fun DeviceDisconnectedView( + reason: Reason, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.(PaddingValues) -> Unit = {}, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedCard( + modifier = Modifier + .widthIn(max = 460.dp), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircularIcon(imageVector = Icons.Default.HighlightOff) + + Text( + text = stringResource(id = R.string.device_disconnected), + style = MaterialTheme.typography.titleMedium + ) + + val text = when (reason) { + Reason.USER -> stringResource(id = R.string.device_reason_user) + Reason.LINK_LOSS -> stringResource(id = R.string.device_reason_link_loss) + Reason.MISSING_SERVICE -> stringResource(id = R.string.device_reason_missing_service) + Reason.UNKNOWN -> stringResource(id = R.string.device_reason_unknown) + } + + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + + content(PaddingValues(top = 16.dp)) + } +} + +@Preview +@Composable +private fun DeviceDisconnectedView_Preview() { + NordicTheme { + DeviceDisconnectedView( + reason = Reason.MISSING_SERVICE, + content = { padding -> + Button( + onClick = {}, + modifier = Modifier.padding(padding) + ) { + Text(text = "Retry") + } + } + ) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/ScannerAppBar.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/ScannerAppBar.kt new file mode 100644 index 00000000..051325f8 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/ScannerAppBar.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.common.theme.view.NordicAppBar + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun ScannerAppBar( + text: String, + showProgress: Boolean = false, + backButtonIcon: ImageVector = Icons.Default.ArrowBack, + onNavigationButtonClick: (() -> Unit)? = null, +) { + NordicAppBar( + text = text, + backButtonIcon = backButtonIcon, + onNavigationButtonClick = onNavigationButtonClick, + actions = { + if (showProgress) { + CircularProgressIndicator( + modifier = Modifier.padding(horizontal = 16.dp).size(30.dp), + color = MaterialTheme.colorScheme.onPrimary + ) + } + }, + ) +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/FilterView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/FilterView.kt new file mode 100644 index 00000000..1b98fe3f --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/FilterView.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view.internal + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Done +import androidx.compose.material.icons.filled.Label +import androidx.compose.material.icons.filled.Widgets +import androidx.compose.material.icons.filled.Wifi +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.scanner.repository.DevicesScanFilter +import no.nordicsemi.android.common.theme.NordicTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun FilterView( + config: DevicesScanFilter, + onChanged: (DevicesScanFilter) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + modifier = modifier, + ) { + config.filterUuidRequired?.let { + ElevatedFilterChip( + selected = !it, + onClick = { onChanged(config.copy(filterUuidRequired = !it)) }, + label = { Text(text = stringResource(id = R.string.filter_uuid),) }, + modifier = Modifier.padding(end = 8.dp), + leadingIcon = { + if (!it) { + Icon(Icons.Default.Done, contentDescription = "") + } else { + Icon(Icons.Default.Widgets, contentDescription = "") + } + }, + ) + } + config.filterNearbyOnly.let { + ElevatedFilterChip( + selected = it, + onClick = { onChanged(config.copy(filterNearbyOnly = !it)) }, + label = { Text(text = stringResource(id = R.string.filter_nearby),) }, + modifier = Modifier.padding(end = 8.dp), + leadingIcon = { + if (it) { + Icon(Icons.Default.Done, contentDescription = "") + } else { + Icon(Icons.Default.Wifi, contentDescription = "") + } + }, + ) + } + config.filterWithNames.let { + ElevatedFilterChip( + selected = it, + onClick = { onChanged(config.copy(filterWithNames = !it)) }, + label = { Text(text = stringResource(id = R.string.filter_name),) }, + modifier = Modifier.padding(end = 8.dp), + leadingIcon = { + if (it) { + Icon(Icons.Default.Done, contentDescription = "") + } else { + Icon(Icons.Default.Label, contentDescription = "") + } + }, + ) + } + } +} + +@Preview +@Composable +private fun FilterViewPreview() { + NordicTheme { + FilterView( + config = DevicesScanFilter( + filterUuidRequired = true, + filterNearbyOnly = true, + filterWithNames = true, + ), + onChanged = {}, + modifier = Modifier.fillMaxWidth(), + ) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanEmptyView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanEmptyView.kt new file mode 100644 index 00000000..3be783a6 --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanEmptyView.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view.internal + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BluetoothSearching +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.common.core.parseBold +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.common.theme.view.WarningView + +@Composable +internal fun ScanEmptyView( + requireLocation: Boolean, +) { + WarningView( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + imageVector = Icons.Default.BluetoothSearching, + title = stringResource(id = R.string.no_device_guide_title), + hint = stringResource(id = R.string.no_device_guide_info) + if (requireLocation) { + "\n\n" + stringResource(id = R.string.no_device_guide_location_info) + } else { + "" + }.parseBold(), + hintTextAlign = TextAlign.Justify, + ) { + if (requireLocation) { + val context = LocalContext.current + Button(onClick = { openLocationSettings(context) }) { + Text(text = stringResource(id = R.string.action_location_settings)) + } + } + } +} + +private fun openLocationSettings(context: Context) { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) +} + +@Preview +@Composable +private fun ScanEmptyViewPreview_RequiredLocation() { + NordicTheme { + ScanEmptyView( + requireLocation = true, + ) + } +} + +@Preview(device = Devices.TABLET) +@Composable +private fun ScanEmptyViewPreview() { + NordicTheme { + ScanEmptyView( + requireLocation = false, + ) + } +} \ No newline at end of file diff --git a/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanErrorView.kt b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanErrorView.kt new file mode 100644 index 00000000..70ba624d --- /dev/null +++ b/scanner/src/main/java/no/nordicsemi/android/scanner/view/internal/ScanErrorView.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.scanner.view.internal + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BluetoothSearching +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.scanner.R +import no.nordicsemi.android.common.theme.NordicTheme +import no.nordicsemi.android.common.theme.view.WarningView +@Composable +internal fun ScanErrorView( + error: Int, +) { + WarningView( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + imageVector = Icons.Default.BluetoothSearching, + title = stringResource(id = R.string.scanner_error), + hint = stringResource(id = R.string.scan_failed, error), + ) +} + +@Preview +@Composable +private fun ErrorSectionPreview() { + NordicTheme { + ScanErrorView(3) + } +} \ No newline at end of file diff --git a/scanner/src/main/res/values/strings.xml b/scanner/src/main/res/values/strings.xml index c5b6a95e..a38b1f90 100644 --- a/scanner/src/main/res/values/strings.xml +++ b/scanner/src/main/res/values/strings.xml @@ -1,4 +1,44 @@ - nRF Blinky + Bluetooth + Location + + Filters + All + Nearby + Name + + No name + Bonded devices + Discovered devices + + Scanning failed with error %d. + Scanner + Scanning failed + Location settings + Back + + CAN\'T SEE YOUR DEVICE? + 1. Make sure the device is turned on and is connected to + a power source.\n\n2. Make sure the appropriate firmware and SoftDevice are flashed. + 3. Location is turned off. Most Android phones + require it in order to scan for Bluetooth LE devices. If you are sure your + device is advertising and it doesn\'t show up here, click the button below to + enable Location. + + App bar navigation back icon. + + Bonding in progress. Please follow instruction on the screen. + Bonding success. Please wait for the redirect to chosen profile screen. + We cannot get data from the peripheral without bonding. Please bond the device. + + Disconnected + Device disconnected successfully. + Device disconnected with unknown reason. + Device signal has been lost. + Device was disconnected, because required services are missing. + + Connecting… + The mobile is trying to connect to peripheral device. + Please wait… \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f433a30b..ae67d8ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ dependencyResolutionManagement { } versionCatalogs { create("libs") { - from("no.nordicsemi.android.gradle:version-catalog:1.4.7") + from("no.nordicsemi.android.gradle:version-catalog:1.9.10") } } } @@ -28,9 +28,9 @@ include(":blinky:spec") include(":blinky:ui") include(":blinky:ble") -//if (file("../Android-Common-Libraries").exists()) { -// includeBuild("../Android-Common-Libraries") -//} +if (file("../Android-Common-Libraries").exists()) { + includeBuild("../Android-Common-Libraries") +} //if (file("../Android-BLE-Library").exists()) { // includeBuild("../Android-BLE-Library") //} From d56e89f044f12c6caa3ce1a1d177d72d22fc663d Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Fri, 29 Sep 2023 12:39:09 +0200 Subject: [PATCH 2/3] Using local BLE library --- settings.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ae67d8ab..c0dc6a01 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,6 +31,6 @@ include(":blinky:ble") if (file("../Android-Common-Libraries").exists()) { includeBuild("../Android-Common-Libraries") } -//if (file("../Android-BLE-Library").exists()) { -// includeBuild("../Android-BLE-Library") -//} +if (file("../Android-BLE-Library").exists()) { + includeBuild("../Android-BLE-Library") +} From 9483b8d61d2d951e07359a47d74bb1fff132e48c Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Fri, 29 Sep 2023 12:40:26 +0200 Subject: [PATCH 3/3] Version catalog 1.9.11 --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index c0dc6a01..795f6505 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ dependencyResolutionManagement { } versionCatalogs { create("libs") { - from("no.nordicsemi.android.gradle:version-catalog:1.9.10") + from("no.nordicsemi.android.gradle:version-catalog:1.9.11") } } }