diff --git a/assets/android/AndroidManifest.xml b/assets/android/AndroidManifest.xml index de2f2ba8..a37da990 100644 --- a/assets/android/AndroidManifest.xml +++ b/assets/android/AndroidManifest.xml @@ -7,17 +7,20 @@ + + - - + + - + + @@ -30,9 +33,10 @@ android:theme="@style/AppTheme" android:icon="@mipmap/ic_launcher" android:label="WatchFlower"> + diff --git a/qml/DeviceList.qml b/qml/DeviceList.qml index 6ff76f7f..e3e51ee3 100644 --- a/qml/DeviceList.qml +++ b/qml/DeviceList.qml @@ -48,16 +48,29 @@ Item { function checkBluetoothStatus() { if (deviceManager.hasDevices) { - if (!deviceManager.bluetoothAdapter || !deviceManager.bluetoothEnabled) { - rectangleBluetoothStatus.setBluetoothWarning() - } else if (!deviceManager.bluetoothPermissions) { - rectangleBluetoothStatus.setPermissionWarning() + // The device list is shown + loaderItemStatus.source = "" + + if (!deviceManager.bluetoothPermissions) { + actionbarBluetoothStatus.setPermissionWarning() + } else if (!deviceManager.bluetoothAdapter) { + actionbarBluetoothStatus.setAdapterWarning() + } else if (!deviceManager.bluetoothEnabled) { + actionbarBluetoothStatus.setBluetoothWarning() } else { - rectangleBluetoothStatus.hide() + actionbarBluetoothStatus.hide() } } else { // The sensor list is not populated - rectangleBluetoothStatus.hide() + actionbarBluetoothStatus.hide() + + if (!deviceManager.bluetoothPermissions) { + loaderItemStatus.source = "ItemNoPermissions.qml" + } else if (!deviceManager.bluetoothAdapter || !deviceManager.bluetoothEnabled) { + loaderItemStatus.source = "ItemNoBluetooth.qml" + } else { + loaderItemStatus.source = "ItemNoDevice.qml" + } } } @@ -120,7 +133,7 @@ Item { //////////////// ActionbarBluetooth { - id: rectangleBluetoothStatus + id: actionbarBluetoothStatus anchors.left: parent.left anchors.right: parent.right } @@ -128,7 +141,7 @@ Item { //////////////// ActionbarSelection { - id: rectangleSelections + id: actionbarSelection anchors.left: parent.left anchors.right: parent.right } @@ -136,19 +149,23 @@ Item { //////////////////////////////////////////////////////////////////////////// - ItemNoDevice { + Loader { + id: loaderDeviceList anchors.fill: parent - visible: !deviceManager.hasDevices + anchors.topMargin: rowbar.height + + visible: deviceManager.hasDevices + asynchronous: false } - //////////////////////////////////////////////////////////////////////////// + //////// Loader { - id: loaderDeviceList + id: loaderItemStatus anchors.fill: parent - anchors.topMargin: rowbar.height - asynchronous: false + visible: !deviceManager.hasDevices + asynchronous: true } //////////////////////////////////////////////////////////////////////////// diff --git a/qml/MobilePermissions.qml b/qml/MobilePermissions.qml index 1f7c0c51..e7504f2c 100644 --- a/qml/MobilePermissions.qml +++ b/qml/MobilePermissions.qml @@ -56,18 +56,78 @@ Item { //////// - Item { - id: element_bluetooth - height: 24 + Item { // Bluetooth anchors.left: parent.left anchors.right: parent.right + height: 24 RoundButtonIcon { + anchors.left: parent.left + anchors.leftMargin: Theme.componentMargin + anchors.verticalCenter: parent.verticalCenter width: 32 height: 32 + + property bool validperm: deviceManager.permissionOS + + source: (validperm) ? "qrc:/assets/icons_material/baseline-check-24px.svg" : "qrc:/assets/icons_material/baseline-close-24px.svg" + iconColor: (validperm) ? "white" : "white" + backgroundColor: (validperm) ? Theme.colorSuccess : Theme.colorSubText + backgroundVisible: true + + onClicked: { + utilsApp.vibrate(25) + utilsApp.getMobileBluetoothPermission() + refreshPermissions.start() + } + } + + Text { + anchors.left: parent.left + anchors.leftMargin: appHeader.headerPosition + anchors.right: parent.right + anchors.rightMargin: Theme.componentMargin + anchors.verticalCenter: parent.verticalCenter + height: 16 + + text: qsTr("Bluetooth") + textFormat: Text.PlainText + wrapMode: Text.WordWrap + font.pixelSize: 17 + color: Theme.colorText + verticalAlignment: Text.AlignVCenter + } + } + Text { // Bluetooth legend + anchors.left: parent.left + anchors.leftMargin: appHeader.headerPosition + anchors.right: parent.right + anchors.rightMargin: Theme.componentMargin + + text: qsTr("The Android operating system requires permission to scan for nearby Bluetooth Low Energy sensors.") + textFormat: Text.StyledText + wrapMode: Text.WordWrap + color: Theme.colorSubText + font.pixelSize: Theme.fontSizeContentSmall + } + + //////// + + ListSeparatorPadded { height: 16+1 } + + //////// + + Item { // Bluetooth control + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + RoundButtonIcon { anchors.left: parent.left anchors.leftMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + width: 32 + height: 32 property bool validperm: true @@ -78,12 +138,12 @@ Item { } Text { - height: 16 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition anchors.right: parent.right anchors.rightMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + height: 16 text: qsTr("Bluetooth control") textFormat: Text.PlainText @@ -113,16 +173,16 @@ Item { //////// Item { // Location - height: 24 anchors.left: parent.left anchors.right: parent.right + height: 24 RoundButtonIcon { - width: 32 - height: 32 anchors.left: parent.left anchors.leftMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + width: 32 + height: 32 property bool validperm: deviceManager.permissionLocationBLE @@ -139,12 +199,12 @@ Item { } Text { - height: 16 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition anchors.right: parent.right anchors.rightMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + height: 16 text: qsTr("Location") textFormat: Text.PlainText @@ -168,9 +228,9 @@ Item { font.pixelSize: Theme.fontSizeContentSmall } ButtonWireframeIcon { - height: 36 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition + height: 36 primaryColor: Theme.colorPrimary secondaryColor: Theme.colorBackground @@ -199,11 +259,11 @@ Item { anchors.right: parent.right RoundButtonIcon { - width: 32 - height: 32 anchors.left: parent.left anchors.leftMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + width: 32 + height: 32 property bool validperm: deviceManager.permissionLocationBackground @@ -220,12 +280,12 @@ Item { } Text { - height: 16 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition anchors.right: parent.right anchors.rightMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + height: 16 text: qsTr("Background location") textFormat: Text.PlainText @@ -254,17 +314,17 @@ Item { //////// - Item { // GPS - height: 24 + Item { // GPS anchors.left: parent.left anchors.right: parent.right + height: 24 RoundButtonIcon { - width: 32 - height: 32 anchors.left: parent.left anchors.leftMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + width: 32 + height: 32 property bool validperm: deviceManager.permissionLocationGPS @@ -280,12 +340,12 @@ Item { } Text { - height: 16 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition anchors.right: parent.right anchors.rightMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + height: 16 text: qsTr("GPS") textFormat: Text.PlainText @@ -309,9 +369,9 @@ Item { } ButtonWireframeIcon { - height: 36 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition + height: 36 primaryColor: Theme.colorPrimary secondaryColor: Theme.colorBackground @@ -331,16 +391,16 @@ Item { Item { id: element_infos - height: 24 anchors.left: parent.left anchors.right: parent.right + height: 32 IconSvg { - width: 32 - height: 32 anchors.left: parent.left anchors.leftMargin: Theme.componentMargin anchors.verticalCenter: parent.verticalCenter + width: 32 + height: 32 opacity: 0.66 color: Theme.colorSubText @@ -352,9 +412,11 @@ Item { anchors.leftMargin: appHeader.headerPosition anchors.right: parent.right anchors.rightMargin: Theme.componentMargin + anchors.verticalCenter: parent.verticalCenter text: qsTr("Click on the checkmarks to request missing permissions.") textFormat: Text.StyledText + lineHeight : 0.8 wrapMode: Text.WordWrap color: Theme.colorText font.pixelSize: Theme.fontSizeContent @@ -376,9 +438,9 @@ Item { } ButtonWireframeIcon { - height: 36 anchors.left: parent.left anchors.leftMargin: appHeader.headerPosition + height: 36 primaryColor: Theme.colorPrimary secondaryColor: Theme.colorBackground diff --git a/qml/components/ActionbarBluetooth.qml b/qml/components/ActionbarBluetooth.qml index d426fc31..6b894422 100644 --- a/qml/components/ActionbarBluetooth.qml +++ b/qml/components/ActionbarBluetooth.qml @@ -18,14 +18,19 @@ Rectangle { function hide() { actionbarBluetoothStatus.height = 0 } - function setBluetoothWarning() { - textBluetoothStatus.text = qsTr("Bluetooth is disabled...") - actionbarBluetoothStatus.height = 52 - } + function setPermissionWarning() { textBluetoothStatus.text = qsTr("Bluetooth permission is missing...") actionbarBluetoothStatus.height = 52 } + function setAdapterWarning() { + textBluetoothStatus.text = qsTr("Bluetooth adapter not found...") + actionbarBluetoothStatus.height = 52 + } + function setBluetoothWarning() { + textBluetoothStatus.text = qsTr("Bluetooth is disabled...") + actionbarBluetoothStatus.height = 52 + } //////////////// @@ -58,19 +63,20 @@ Rectangle { primaryColor: Theme.colorActionbarHighlight text: { + if (!deviceManager.bluetoothPermissions) return qsTr("Request") if (Qt.platform.os === "android") { if (!deviceManager.bluetoothEnabled) return qsTr("Enable") - else if (!deviceManager.bluetoothPermissions) return qsTr("About") } return qsTr("Retry") } onClicked: { - if (Qt.platform.os === "android" && !deviceManager.bluetoothPermissions) { - // someone clicked 'never ask again' on the Bluetooth permission? - screenAboutPermissions.loadScreenFrom("DeviceList") - } else { + if (!deviceManager.bluetoothPermissions) { + deviceManager.requestBluetoothPermissions() + } + if (!deviceManager.bluetoothEnabled) { deviceManager.enableBluetooth(settingsManager.bluetoothControl) } + deviceManager.checkBluetooth() } } diff --git a/qml/components/ItemNoBluetooth.qml b/qml/components/ItemNoBluetooth.qml new file mode 100644 index 00000000..ad9192ce --- /dev/null +++ b/qml/components/ItemNoBluetooth.qml @@ -0,0 +1,119 @@ +import QtQuick +import QtQuick.Layouts + +import ThemeEngine + +Item { + id: itemNoBluetooth + anchors.fill: parent + + Column { + anchors.left: parent.left + anchors.leftMargin: Theme.componentMarginXL + anchors.right: parent.right + anchors.rightMargin: Theme.componentMarginXL + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -Theme.componentMarginXL + spacing: Theme.componentMargin + + //// + + Column { + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + + width: singleColumn ? (itemNoBluetooth.width*0.5) : (itemNoBluetooth.height*0.4) + height: width + radius: width + color: Theme.colorForeground + + IconSvg { // bluetooth disabled icon + anchors.centerIn: parent + width: parent.width*0.8 + height: width + + source: "qrc:/assets/icons_material/baseline-bluetooth_disabled-24px.svg" + fillMode: Image.PreserveAspectFit + color: Theme.colorSubText + opacity: 0.9 + smooth: true + } + } + + Item { width: Theme.componentMarginXL; height: Theme.componentMarginXL; } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + + text: { + if (deviceManager.bluetoothAdapter && !deviceManager.bluetoothEnabled) { + return qsTr("Bluetooth is disabled...") + } + return qsTr("Bluetooth adapter not found...") + } + textFormat: Text.PlainText + font.pixelSize: Theme.fontSizeContentBig + color: Theme.colorText + } + + Item { width: 8; height: 8; } + + ButtonWireframe { + anchors.horizontalCenter: parent.horizontalCenter + + fullColor: true + text: { + if (deviceManager.bluetoothAdapter &&!deviceManager.bluetoothEnabled) { + if (Qt.platform.os === "android") return qsTr("Enable") + } + return qsTr("Retry") + } + onClicked: { + if (deviceManager.bluetoothAdapter &&!deviceManager.bluetoothEnabled) { + if (Qt.platform.os === "android") { + deviceManager.enableBluetooth() + return + } + } + deviceManager.checkBluetooth() + } + } + + Item { width: 8; height: 8; } + } + + //// + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoBluetooth.width*0.85) : undefined + spacing: Theme.componentMargin + + visible: !deviceManager.bluetoothEnabled + + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + + source: "qrc:/assets/icons_material/baseline-warning-24px.svg" + color: Theme.colorWarning + } + Text { + Layout.fillWidth: singleColumn + Layout.alignment: Qt.AlignVCenter + + text: qsTr("Please enable Bluetooth on your device in order to use the application.") + textFormat: Text.StyledText + font.pixelSize: Theme.fontSizeContentSmall + color: Theme.colorSubText + wrapMode: Text.WordWrap + horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + } + } + + //// + } +} diff --git a/qml/components/ItemNoDevice.qml b/qml/components/ItemNoDevice.qml index 7eca0a9e..e9c66a4b 100644 --- a/qml/components/ItemNoDevice.qml +++ b/qml/components/ItemNoDevice.qml @@ -7,28 +7,6 @@ Item { id: itemNoDevice anchors.fill: parent - //////////////////////////////////////////////////////////////////////////// - - Timer { - id: retryScan - interval: 333 - running: false - repeat: false - onTriggered: scan() - } - - function scan() { - if (!deviceManager.updating) { - if (deviceManager.scanning) { - deviceManager.scanDevices_stop() - } else { - deviceManager.scanDevices_start() - } - } - } - - //////////////////////////////////////////////////////////////////////////// - Column { anchors.left: parent.left anchors.right: parent.right @@ -58,146 +36,71 @@ Item { Column { anchors.left: parent.left anchors.right: parent.right - spacing: Theme.componentMarginXL - - //////// + spacing: Theme.componentMarginXL + 8 - ColumnLayout { - anchors.left: parent.left - anchors.leftMargin: Theme.componentMargin*2.5 + 20 - anchors.right: parent.right - anchors.rightMargin: Theme.componentMargin*2.5 - spacing: Theme.componentMargin/2 + //// - //// + ButtonWireframe { + anchors.horizontalCenter: parent.horizontalCenter - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter + width: (isDesktop || isTablet || (isPhone && screenOrientation === Qt.LandscapeOrientation)) ? 320 : (parent.width*0.5) - visible: !deviceManager.bluetoothEnabled + text: deviceManager.scanning ? qsTr("Scanning...") : qsTr("Launch detection") + fullColor: true + primaryColor: Theme.colorPrimary - IconSvg { - anchors.right: parent.left - anchors.rightMargin: Theme.componentMargin - anchors.verticalCenter: parent.verticalCenter - width: 20; height: 20; - source: "qrc:/assets/icons_material/baseline-warning-24px.svg" - color: Theme.colorWarning + onClicked: { + // Just to be sure... + if (!deviceManager.bluetoothPermissions) { + // Ask permission + utilsApp.getMobileBleLocationPermission() } - - text: qsTr("Please enable Bluetooth on your device in order to use the application.") - textFormat: Text.StyledText - font.pixelSize: Theme.fontSizeContentSmall - color: Theme.colorSubText - wrapMode: Text.WordWrap - horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter - } - - //// - - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter - - visible: (Qt.platform.os === "osx" || Qt.platform.os === "ios") - - IconSvg { - anchors.right: parent.left - anchors.rightMargin: Theme.componentMargin - anchors.verticalCenter: parent.verticalCenter - width: 20; height: 20; - source: "qrc:/assets/icons_material/baseline-warning-24px.svg" - color: Theme.colorWarning + if (!deviceManager.bluetoothAdapter || !deviceManager.bluetoothEnabled) { + // Enable + deviceManager.enableBluetooth(true) } - text: qsTr("Authorization to use Bluetooth is required to connect to the sensors.") - textFormat: Text.StyledText - font.pixelSize: Theme.fontSizeContentSmall - color: Theme.colorSubText - wrapMode: Text.WordWrap - horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + // Now we scan... + tryScan.start() } - //// - - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter - - visible: (Qt.platform.os === "android") - - IconSvg { - anchors.right: parent.left - anchors.rightMargin: Theme.componentMargin - anchors.verticalCenter: parent.verticalCenter - width: 20; height: 20; - source: "qrc:/assets/icons_material/baseline-warning-24px.svg" - color: Theme.colorWarning + Timer { + id: tryScan + interval: 333 + running: false + repeat: false + onTriggered: { + if (!deviceManager.updating) { + if (deviceManager.scanning) { + deviceManager.scanDevices_stop() + } else { + deviceManager.scanDevices_start() + } + } } - - text: qsTr("On Android 6+, scanning for Bluetooth Low Energy devices requires location permission.") - textFormat: Text.StyledText - font.pixelSize: Theme.fontSizeContentSmall - color: Theme.colorSubText - wrapMode: Text.WordWrap - horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter - } - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter - - visible: (Qt.platform.os === "android") - - text: qsTr("The application is neither using nor storing your location. Sorry for the inconvenience.") - textFormat: Text.PlainText - font.pixelSize: Theme.fontSizeContentSmall - color: Theme.colorSubText - wrapMode: Text.WordWrap - horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter } + } - //// + //// - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoDevice.width*0.85) : undefined + spacing: Theme.componentMargin - visible: (Qt.platform.os === "android" && !deviceManager.permissionLocationGPS) + //visible: !deviceManager.bluetoothEnabled - IconSvg { - anchors.right: parent.left - anchors.rightMargin: Theme.componentMargin - anchors.verticalCenter: parent.verticalCenter - width: 20; height: 20; - source: "qrc:/assets/icons_material/baseline-warning-24px.svg" - color: Theme.colorSubText - } + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter - text: qsTr("Some Android devices also require the actual GPS to be turned on.") - textFormat: Text.StyledText - font.pixelSize: Theme.fontSizeContentSmall + source: "qrc:/assets/icons_material/baseline-info-24px.svg" color: Theme.colorSubText - wrapMode: Text.WordWrap - horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter } - - //// - Text { - Layout.maximumWidth: parent.width - Layout.alignment: Qt.AlignHCenter - - visible: settingsManager.bluetoothLimitScanningRange - - IconSvg { - anchors.right: parent.left - anchors.rightMargin: Theme.componentMargin - anchors.verticalCenter: parent.verticalCenter - width: 20; height: 20; - source: "qrc:/assets/icons_material/baseline-info-24px.svg" - color: Theme.colorSubText - } + Layout.fillWidth: singleColumn + Layout.alignment: Qt.AlignVCenter text: qsTr("Please keep your device close to the sensors you want to scan.") textFormat: Text.StyledText @@ -206,103 +109,9 @@ Item { wrapMode: Text.WordWrap horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter } - - //// - } - - //////// - - Grid { - anchors.horizontalCenter: parent.horizontalCenter - - visible: isMobile - spacing: Theme.componentMarginXL - - rows: 2 - columns: singleColumn ? 1 : 2 - - Item { - width: singleColumn ? contentColumn.width : btn1.width - height: Theme.componentHeight - - ButtonWireframeIcon { - id: btn1 - anchors.horizontalCenter: parent.horizontalCenter - - //width: (isDesktop || isTablet || (isPhone && appWindow.screenOrientation === Qt.LandscapeOrientation)) ? undefined : (parent.width*0.75) - - text: qsTr("Official information") - primaryColor: Theme.colorSubText - sourceSize: 20 - source: "qrc:/assets/icons_material/duotone-launch-24px.svg" - - onClicked: { - if (Qt.platform.os === "android") { - if (utilsApp.getAndroidSdkVersion() >= 12) - Qt.openUrlExternally("https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android12-or-higher") - else - Qt.openUrlExternally("https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android11-or-lower") - } else if (Qt.platform.os === "ios") { - Qt.openUrlExternally("https://support.apple.com/HT210578") - } - } - } - } - - Item { - width: singleColumn ? contentColumn.width : btn2.width - height: Theme.componentHeight - - ButtonWireframe { - id: btn2 - anchors.horizontalCenter: parent.horizontalCenter - - //width: (isDesktop || isTablet || (isPhone && appWindow.screenOrientation === Qt.LandscapeOrientation)) ? undefined : (parent.width*0.75) - - text: deviceManager.scanning ? qsTr("Scanning...") : qsTr("Launch detection") - fullColor: true - primaryColor: Theme.colorPrimary - - onClicked: { - if (!deviceManager.bluetoothAdapter || !deviceManager.bluetoothEnabled) { - // Just to be sure... - deviceManager.enableBluetooth(true) - } - - if (!deviceManager.bluetoothPermissions) { - // Ask permission - utilsApp.getMobileBleLocationPermission() - } - - // Now we scan... - retryScan.start() - } - } - } - } - - //////// - - ButtonWireframe { // desktop only launch button - anchors.horizontalCenter: parent.horizontalCenter - width: 320 - - visible: isDesktop - fullColor: true - primaryColor: Theme.colorPrimary - - text: deviceManager.scanning ? qsTr("Scanning...") : qsTr("Launch detection") - - onClicked: { - // Just to be sure... - deviceManager.enableBluetooth() - - // Now we scan... - retryScan.start() - } } - //////// + //// } } diff --git a/qml/components/ItemNoPermissions.qml b/qml/components/ItemNoPermissions.qml new file mode 100644 index 00000000..1bf2f950 --- /dev/null +++ b/qml/components/ItemNoPermissions.qml @@ -0,0 +1,230 @@ +import QtQuick +import QtQuick.Layouts + +import ThemeEngine + +Item { + id: itemNoPermissions + anchors.fill: parent + + Column { + anchors.left: parent.left + anchors.leftMargin: Theme.componentMarginXL + anchors.right: parent.right + anchors.rightMargin: Theme.componentMarginXL + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -Theme.componentMarginXL + spacing: Theme.componentMargin + + //// + + Column { + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + + width: singleColumn ? (itemNoPermissions.width*0.5) : (itemNoPermissions.height*0.4) + height: width + radius: width + color: Theme.colorForeground + + IconSvg { // lock icon + anchors.centerIn: parent + width: parent.width*0.8 + height: width + + source: "qrc:/assets/icons_material/outline-lock-24px.svg" + fillMode: Image.PreserveAspectFit + color: Theme.colorSubText + opacity: 0.9 + smooth: true + } + } + + Item { width: Theme.componentMarginXL; height: Theme.componentMarginXL; } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + + text: qsTr("Bluetooth permission(s) missing…") + textFormat: Text.PlainText + font.pixelSize: Theme.fontSizeContentBig + color: Theme.colorText + } + + Item { width: 8; height: 8; } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: Theme.componentMargin + + ButtonWireframeIcon { + //width: ((isDesktop || isTablet) && !singleColumn) ? 256 : undefined + + text: qsTr("Request permission(s)") + primaryColor: Theme.colorPrimary + fullColor: true + sourceSize: 24 + source: "qrc:/assets/icons_material/duotone-touch_app-24px.svg" + + onClicked: { + deviceManager.requestBluetoothPermissions() + } + } + } + + Item { width: 8; height: 8; } + } + + //// + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoPermissions.width*0.85) : undefined + spacing: Theme.componentMargin + + visible: (Qt.platform.os === "android" || Qt.platform.os === "ios" || Qt.platform.os === "osx") + + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + + source: "qrc:/assets/icons_material/baseline-warning-24px.svg" + color: Theme.colorWarning + } + Text { + Layout.fillWidth: singleColumn + Layout.alignment: Qt.AlignVCenter + + text: qsTr("Authorization to use Bluetooth is required to connect to the sensors.") + textFormat: Text.StyledText + font.pixelSize: Theme.fontSizeContentSmall + color: Theme.colorSubText + wrapMode: Text.WordWrap + horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + } + } + + //// + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoPermissions.width*0.85) : undefined + spacing: Theme.componentMargin + + visible: (Qt.platform.os === "android") + + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + + source: "qrc:/assets/icons_material/baseline-warning-24px.svg" + color: Theme.colorWarning + } + Text { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + + text: qsTr("On Android 6+, scanning for Bluetooth Low Energy devices requires location permission.") + textFormat: Text.StyledText + font.pixelSize: Theme.fontSizeContentSmall + color: Theme.colorSubText + wrapMode: Text.WordWrap + horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + } + } + + //// + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoPermissions.width*0.85) : undefined + spacing: Theme.componentMargin + + visible: (Qt.platform.os === "android" && !deviceManager.permissionLocationGPS) + + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + + source: "qrc:/assets/icons_material/baseline-warning-24px.svg" + color: Theme.colorSubText + } + + Text { + Layout.fillWidth: singleColumn + Layout.alignment: Qt.AlignVCenter + + text: qsTr("Some Android devices also require the actual GPS to be turned on.") + textFormat: Text.StyledText + font.pixelSize: Theme.fontSizeContentSmall + color: Theme.colorSubText + wrapMode: Text.WordWrap + horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + } + } + + //// + + RowLayout { + anchors.horizontalCenter: parent.horizontalCenter + width: singleColumn ? (itemNoPermissions.width*0.85) : undefined + spacing: Theme.componentMargin + + visible: (Qt.platform.os === "android") + + IconSvg { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + + source: "qrc:/assets/icons_material/baseline-info-24px.svg" + color: Theme.colorSubText + } + Text { + Layout.fillWidth: singleColumn + Layout.alignment: Qt.AlignVCenter + + text: qsTr("The application is neither using nor storing your location. Sorry for the inconvenience.") + textFormat: Text.PlainText + font.pixelSize: Theme.fontSizeContentSmall + color: Theme.colorSubText + wrapMode: Text.WordWrap + horizontalAlignment: singleColumn ? Text.AlignJustify : Text.AlignHCenter + } + } + + //// + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: Theme.componentMargin + + ButtonWireframeIcon { + //width: ((isDesktop || isTablet) && !singleColumn) ? 256 : undefined + + text: qsTr("Official information") + primaryColor: Theme.colorSubText + sourceSize: 20 + source: "qrc:/assets/icons_material/duotone-launch-24px.svg" + + onClicked: { + if (Qt.platform.os === "android") { + if (utilsApp.getAndroidSdkVersion() >= 12) + Qt.openUrlExternally("https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android12-or-higher") + else + Qt.openUrlExternally("https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android11-or-lower") + } else if (Qt.platform.os === "ios") { + Qt.openUrlExternally("https://support.apple.com/HT210578") + } + } + } + } + + //// + } +} diff --git a/qml/qml.qrc b/qml/qml.qrc index d64ffeab..b04880aa 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -61,9 +61,11 @@ components/ItemEnvBox.qml components/ItemWeatherBox.qml components/ItemLoadData.qml - components/ItemNoData.qml + components/ItemNoBluetooth.qml + components/ItemNoPermissions.qml components/ItemNoDevice.qml components/ItemNoDeviceNearby.qml + components/ItemNoData.qml components/ItemNoPlant.qml components/ItemNoPlants.qml components/ItemNoJournal.qml diff --git a/src/DeviceManager.cpp b/src/DeviceManager.cpp index 09fcf4ab..a72a10d5 100644 --- a/src/DeviceManager.cpp +++ b/src/DeviceManager.cpp @@ -62,7 +62,7 @@ #include #include -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) #if QT_CONFIG(permissions) #include #include @@ -313,6 +313,10 @@ bool DeviceManager::enableBluetooth(bool enforceUserPermissionCheck) if (m_bluetoothAdapter && !m_bluetoothAdapter->isValid()) { qDebug() << "DeviceManager::enableBluetooth() deleting current adapter"; + + disconnect(m_bluetoothAdapter, &QBluetoothLocalDevice::hostModeStateChanged, + this, &DeviceManager::bluetoothHostModeStateChanged); + delete m_bluetoothAdapter; m_bluetoothAdapter = nullptr; } @@ -366,6 +370,7 @@ bool DeviceManager::enableBluetooth(bool enforceUserPermissionCheck) } else { + qWarning() << "DeviceManager::enableBluetooth() we have an invalid adapter"; m_bleAdapter = false; m_bleEnabled = false; } @@ -386,11 +391,16 @@ bool DeviceManager::checkBluetoothPermissions() { //qDebug() << "DeviceManager::checkBluetoothPermissions()"; -#if !defined(Q_OS_MACOS) && !defined(Q_OS_IOS) +#if defined(Q_OS_ANDROID) + // +#elif defined(Q_OS_LINUX) || defined(Q_OS_WINDOWS) + // These OS don't ask for any particular permissions m_permOS = true; + m_blePermissions = true; #endif #if !defined(Q_OS_ANDROID) + // The location permission(s) debacle is Android only m_permLocationBLE = true; m_permLocationBKG = true; m_permGPS = true; @@ -403,44 +413,33 @@ bool DeviceManager::checkBluetoothPermissions() bool btP_was = m_blePermissions; #if defined(Q_OS_ANDROID) - + m_permOS = UtilsApp::checkMobileBluetoothPermission(); m_permLocationBLE = UtilsApp::checkMobileBleLocationPermission(); m_permLocationBKG = UtilsApp::checkMobileBackgroundLocationPermission(); m_permGPS = UtilsApp::isMobileGpsEnabled(); - // set m_permLocationBLE as primary - // we will check for GPS or background location permissions explicitely if we need them - m_blePermissions = m_permLocationBLE; + m_blePermissions = m_permOS && m_permLocationBLE; +#endif -#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) #if QT_CONFIG(permissions) - if (qApp) { - QBluetoothPermission blePermission; - switch (qApp->checkPermission(blePermission)) + switch (qApp->checkPermission(QBluetoothPermission{})) { case Qt::PermissionStatus::Undetermined: - qApp->requestPermission(blePermission, this, &DeviceManager::checkBluetoothPermissions); - return false; case Qt::PermissionStatus::Denied: m_permOS = false; - m_blePermissions = m_permOS; + m_blePermissions = false; break; case Qt::PermissionStatus::Granted: m_permOS = true; - m_blePermissions = m_permOS; + m_blePermissions = true; break; } } - #endif // QT_CONFIG(permissions) -#else - - // Linux and Windows don't have required BLE permissions - m_blePermissions = true; - -#endif +#endif // defined(Q_OS_MACOS) || defined(Q_OS_IOS) if (os_was != m_permOS || gps_was != m_permGPS || loc_was != m_permLocationBLE || loc_bg_was != m_permLocationBKG) @@ -457,11 +456,91 @@ bool DeviceManager::checkBluetoothPermissions() return m_blePermissions; } +bool DeviceManager::requestBluetoothPermissions() +{ + //qDebug() << "DeviceManager::requestBluetoothPermissions()"; + +#if defined(Q_OS_ANDROID) +#if QT_CONFIG(permissions) + + // qApp->checkPermission(QBluetoothPermission{}) doesn't work on Android + // so we do it ourselves, the old fashioned way... + + bool permLocationBLE = UtilsApp::checkMobileBleLocationPermission(); + bool permOS = UtilsApp::checkMobileBluetoothPermission(); + + if (!permLocationBLE || !permOS) + { + if (qApp) + { + qApp->requestPermission(QBluetoothPermission{}, this, &DeviceManager::requestBluetoothPermissions_results); + } + } + +#else // QT_CONFIG(permissions) + + m_permOS = UtilsApp::getMobileBluetoothPermission(); + m_permLocationBLE = UtilsApp::getMobileBleLocationPermission(); + m_blePermissions = m_permOS && m_permLocationBLE; + +#endif // QT_CONFIG(permissions) +#endif // defined(Q_OS_ANDROID) + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) +#if QT_CONFIG(permissions) + + if (qApp) + { + switch (qApp->checkPermission(QBluetoothPermission{})) + { + case Qt::PermissionStatus::Undetermined: + qDebug() << "Qt::PermissionStatus::Undetermined"; + qApp->requestPermission(QBluetoothPermission{}, this, &DeviceManager::requestBluetoothPermissions_results); + break; + case Qt::PermissionStatus::Granted: + qDebug() << "Qt::PermissionStatus::Granted"; + m_permOS = true; + m_blePermissions = true; + break; + case Qt::PermissionStatus::Denied: + qDebug() << "Qt::PermissionStatus::Denied"; + m_permOS = false; + m_blePermissions = false; + break; + } + } + +#endif // QT_CONFIG(permissions) +#endif // defined(Q_OS_MACOS) || defined(Q_OS_IOS) + + return m_blePermissions; +} + +void DeviceManager::requestBluetoothPermissions_results() +{ + // evaluate the results + checkBluetooth(); + + if (m_blePermissions) + { + // try enabling the adapter + if (!m_bleAdapter || !m_bleEnabled) + { + enableBluetooth(); + } + } + else + { + // try again? + //requestBluetoothPermissions(); + } +} + /* ************************************************************************** */ void DeviceManager::bluetoothHostModeStateChanged(QBluetoothLocalDevice::HostMode state) { - qDebug() << "DeviceManager::bluetoothHostModeStateChanged() host mode now:" << state; + //qDebug() << "DeviceManager::bluetoothHostModeStateChanged() host mode now:" << state; if (state != m_ble_hostmode) { @@ -505,6 +584,20 @@ void DeviceManager::bluetoothStatusChanged() } } +void DeviceManager::bluetoothPermissionsChanged() +{ + //qDebug() << "DeviceManager::bluetoothPermissionsChanged()"; + + if (m_bleAdapter && m_bleEnabled) + { + checkBluetooth(); + } + else + { + enableBluetooth(); + } +} + /* ************************************************************************** */ /* ************************************************************************** */ @@ -1486,8 +1579,9 @@ void DeviceManager::addBleDevice(const QBluetoothDeviceInfo &info) // Various sanity checks { if (info.rssi() >= 0) return; // we probably just hit the device cache - + //if ((info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) == false) return; // not a BLE device if (m_devices_blacklist.contains(info.address().toString())) return; // device is blacklisted + if (m_devices_blacklist.contains(info.deviceUuid().toString())) return; // device is blacklisted SettingsManager *sm = SettingsManager::getInstance(); if (sm && sm->getBluetoothLimitScanningRange() && info.rssi() < -70) return; // device is too far away @@ -1610,7 +1704,7 @@ void DeviceManager::addBleDevice(const QBluetoothDeviceInfo &info) } } - // + // Connect and handle update connect(d, &Device::deviceUpdated, this, &DeviceManager::refreshDevices_finished); connect(d, &Device::deviceSynced, this, &DeviceManager::syncDevices_finished); diff --git a/src/DeviceManager.h b/src/DeviceManager.h index 68f122d2..bbb15f5a 100644 --- a/src/DeviceManager.h +++ b/src/DeviceManager.h @@ -90,9 +90,9 @@ class DeviceManager: public QObject bool m_bleAdapter = false; //!< do we have a BLE adapter? bool m_bleEnabled = false; //!< is the BLE adapter enabled? - bool m_blePermissions = false; //!< do we have necessary BLE permissions? (OS independent) + bool m_blePermissions = false; //!< do we have necessary BLE permissions? (brings together all other permsissions) - bool m_permOS = false; //!< do we have OS permissions for BLE? (macOS, iOS) + bool m_permOS = false; //!< do we have OS permissions for BLE? (macOS, iOS, Android) bool m_permLocationBLE = false; //!< do we location permission? (Android) bool m_permLocationBKG = false; //!< do we background location permission? (Android) bool m_permGPS = false; //!< is the GPS enabled? (Android) @@ -164,6 +164,7 @@ class DeviceManager: public QObject Q_SIGNALS: void bluetoothChanged(); + void hostModeChanged(); void permissionsChanged(); void adaptersListUpdated(); @@ -177,12 +178,12 @@ class DeviceManager: public QObject void scanningChanged(); void updatingChanged(); void syncingChanged(); - void hostModeChanged(); private slots: // QBluetoothLocalDevice related void bluetoothHostModeStateChanged(QBluetoothLocalDevice::HostMode); void bluetoothStatusChanged(); + void bluetoothPermissionsChanged(); // QBluetoothDeviceDiscoveryAgent related void addNearbyBleDevice(const QBluetoothDeviceInfo &info); @@ -210,6 +211,8 @@ private slots: Q_INVOKABLE bool checkBluetooth(); Q_INVOKABLE bool checkBluetoothPermissions(); Q_INVOKABLE bool enableBluetooth(bool enforceUserPermissionCheck = false); + Q_INVOKABLE bool requestBluetoothPermissions(); + void requestBluetoothPermissions_results(); // Scanning management static int getLastRun(); diff --git a/src/thirdparty/AppUtils/utils_app.cpp b/src/thirdparty/AppUtils/utils_app.cpp index b12bed40..35ead48e 100644 --- a/src/thirdparty/AppUtils/utils_app.cpp +++ b/src/thirdparty/AppUtils/utils_app.cpp @@ -253,9 +253,9 @@ int UtilsApp::getAndroidSdkVersion() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getSdkVersion(); -#else - return 0; #endif + + return 0; } void UtilsApp::openAndroidAppInfo(const QString &packageName) @@ -283,60 +283,84 @@ void UtilsApp::openAndroidLocationSettings() #endif } +/* ************************************************************************** */ + +bool UtilsApp::checkMobileBluetoothPermission() +{ +#if defined(Q_OS_ANDROID) + return UtilsAndroid::checkPermission_bluetooth(); +#elif defined(Q_OS_IOS) + #warning("Please use Qt permission system directly on iOS") + return false; +#endif + + return true; +} + +bool UtilsApp::getMobileBluetoothPermission() +{ +#if defined(Q_OS_ANDROID) + return UtilsAndroid::getPermission_bluetooth(); +#elif defined(Q_OS_IOS) + #warning("Please use Qt permission system directly on iOS") + return false; +#endif + + return true; +} + bool UtilsApp::checkMobileLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::checkPermission_location(); -#else - return true; #endif + + return true; } bool UtilsApp::getMobileLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getPermission_location(); -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileBleLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::checkPermission_location_ble(); -#elif defined(Q_OS_IOS) - return true; // TODO // we know have Bluetooth permission on iOS too -#else - return true; #endif + + return true; } bool UtilsApp::getMobileBleLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getPermission_location_ble(); -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileBackgroundLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::checkPermission_location_background(); -#else - return true; #endif + + return true; } bool UtilsApp::getMobileBackgroundLocationPermission() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getPermission_location_background(); -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileStoragePermissions() @@ -345,9 +369,9 @@ bool UtilsApp::checkMobileStoragePermissions() return UtilsAndroid::checkPermissions_storage(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobileStoragePermissions() @@ -356,9 +380,9 @@ bool UtilsApp::getMobileStoragePermissions() return UtilsAndroid::getPermissions_storage(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileStorageReadPermission() @@ -367,9 +391,9 @@ bool UtilsApp::checkMobileStorageReadPermission() return UtilsAndroid::checkPermission_storage_read(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobileStorageReadPermission() @@ -378,9 +402,9 @@ bool UtilsApp::getMobileStorageReadPermission() return UtilsAndroid::getPermission_storage_read(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileStorageWritePermission() @@ -389,9 +413,9 @@ bool UtilsApp::checkMobileStorageWritePermission() return UtilsAndroid::checkPermission_storage_write(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobileStorageWritePermission() @@ -400,9 +424,9 @@ bool UtilsApp::getMobileStorageWritePermission() return UtilsAndroid::getPermission_storage_write(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::checkMobileStorageFileSystemPermission() @@ -411,9 +435,9 @@ bool UtilsApp::checkMobileStorageFileSystemPermission() return UtilsAndroid::checkPermission_storage_filesystem(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobileStorageFileSystemPermission(const QString &packageName) @@ -424,9 +448,9 @@ bool UtilsApp::getMobileStorageFileSystemPermission(const QString &packageName) return UtilsAndroid::getPermission_storage_filesystem(packageName); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::checkMobilePhoneStatePermission() @@ -435,9 +459,9 @@ bool UtilsApp::checkMobilePhoneStatePermission() return UtilsAndroid::checkPermission_phonestate(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobilePhoneStatePermission() @@ -446,9 +470,9 @@ bool UtilsApp::getMobilePhoneStatePermission() return UtilsAndroid::getPermission_phonestate(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } /* ************************************************************************** */ @@ -459,9 +483,9 @@ bool UtilsApp::checkMobileCameraPermission() return UtilsAndroid::checkPermission_camera(); #elif defined(Q_OS_IOS) return false; -#else - return true; #endif + + return true; } bool UtilsApp::getMobileCameraPermission() @@ -470,9 +494,33 @@ bool UtilsApp::getMobileCameraPermission() return UtilsAndroid::getPermission_camera(); #elif defined(Q_OS_IOS) return false; -#else +#endif + return true; +} + +/* ************************************************************************** */ + +bool UtilsApp::checkMobileNotificationPermission() +{ +#if defined(Q_OS_ANDROID) + return UtilsAndroid::getPermission_notification(); +#elif defined(Q_OS_IOS) + return false; #endif + + return true; +} + +bool UtilsApp::getMobileNotificationPermission() +{ +#if defined(Q_OS_ANDROID) + return UtilsAndroid::getPermission_notification(); +#elif defined(Q_OS_IOS) + return false; +#endif + + return true; } /* ************************************************************************** */ @@ -482,10 +530,10 @@ bool UtilsApp::isMobileGpsEnabled() #if defined(Q_OS_ANDROID) return UtilsAndroid::gpsutils_isGpsEnabled(); #elif defined(Q_OS_IOS) - return false; // TODO -#else - return false; + return false; // TODO? #endif + + return false; } void UtilsApp::forceMobileGpsEnabled() @@ -501,18 +549,18 @@ QString UtilsApp::getMobileDeviceModel() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getDeviceModel(); -#else - return QString(); #endif + + return QString(); } QString UtilsApp::getMobileDeviceSerial() { #if defined(Q_OS_ANDROID) return UtilsAndroid::getDeviceSerial(); -#else - return QString(); #endif + + return QString(); } /* ************************************************************************** */ diff --git a/src/thirdparty/AppUtils/utils_app.h b/src/thirdparty/AppUtils/utils_app.h index 6514b0cf..397eb145 100644 --- a/src/thirdparty/AppUtils/utils_app.h +++ b/src/thirdparty/AppUtils/utils_app.h @@ -92,6 +92,9 @@ class UtilsApp : public QObject static Q_INVOKABLE bool checkMobileStorageFileSystemPermission(); static Q_INVOKABLE bool getMobileStorageFileSystemPermission(const QString &packageName); + static Q_INVOKABLE bool checkMobileBluetoothPermission(); + static Q_INVOKABLE bool getMobileBluetoothPermission(); + static Q_INVOKABLE bool checkMobileLocationPermission(); static Q_INVOKABLE bool getMobileLocationPermission(); @@ -107,6 +110,9 @@ class UtilsApp : public QObject static Q_INVOKABLE bool checkMobileCameraPermission(); static Q_INVOKABLE bool getMobileCameraPermission(); + static Q_INVOKABLE bool checkMobileNotificationPermission(); + static Q_INVOKABLE bool getMobileNotificationPermission(); + static Q_INVOKABLE bool isMobileGpsEnabled(); static Q_INVOKABLE void forceMobileGpsEnabled(); diff --git a/src/thirdparty/AppUtils/utils_os_android.h b/src/thirdparty/AppUtils/utils_os_android.h index 579e9de8..6d52d6d6 100644 --- a/src/thirdparty/AppUtils/utils_os_android.h +++ b/src/thirdparty/AppUtils/utils_os_android.h @@ -78,6 +78,26 @@ class UtilsAndroid */ static bool getPermission_camera(); + /*! + * \return True if POST_NOTIFICATIONS permission has been previously obtained. + */ + static bool checkPermission_notification(); + + /*! + * \return True if POST_NOTIFICATIONS permission has been explicitly obtained. + */ + static bool getPermission_notification(); + + /*! + * \return True if Bluetooth permission has been previously obtained. + */ + static bool checkPermission_bluetooth(); + + /*! + * \return True if Bluetooth permission has been explicitly obtained. + */ + static bool getPermission_bluetooth(); + /*! * \return True if ACCESS_FINE_LOCATION permission has been previously obtained. */ diff --git a/src/thirdparty/AppUtils/utils_os_android_qt5.cpp b/src/thirdparty/AppUtils/utils_os_android_qt5.cpp index ee6316b6..e37c91fb 100644 --- a/src/thirdparty/AppUtils/utils_os_android_qt5.cpp +++ b/src/thirdparty/AppUtils/utils_os_android_qt5.cpp @@ -164,6 +164,30 @@ bool UtilsAndroid::getPermission_camera() /* ************************************************************************** */ +bool UtilsAndroid::checkPermission_notification() +{ + return false; // TODO +} + +bool UtilsAndroid::getPermission_notification() +{ + return false; // TODO +} + +/* ************************************************************************** */ + +bool UtilsAndroid::checkPermission_bluetooth() +{ + return false; // TODO +} + +bool UtilsAndroid::getPermission_bluetooth() +{ + return false; // TODO +} + +/* ************************************************************************** */ + bool UtilsAndroid::checkPermission_location() { QtAndroid::PermissionResult loc = QtAndroid::checkPermission("android.permission.ACCESS_FINE_LOCATION"); diff --git a/src/thirdparty/AppUtils/utils_os_android_qt6.cpp b/src/thirdparty/AppUtils/utils_os_android_qt6.cpp index b486553a..661e228d 100644 --- a/src/thirdparty/AppUtils/utils_os_android_qt6.cpp +++ b/src/thirdparty/AppUtils/utils_os_android_qt6.cpp @@ -189,6 +189,113 @@ bool UtilsAndroid::getPermission_camera() /* ************************************************************************** */ +bool UtilsAndroid::checkPermission_notification() +{ + QFuture notif = QtAndroidPrivate::checkPermission("android.permission.POST_NOTIFICATIONS"); + //cam.waitForFinished(); + + return (notif.result() == QtAndroidPrivate::PermissionResult::Authorized); +} + +bool UtilsAndroid::getPermission_notification() +{ + bool status = true; + + QFuture notif = QtAndroidPrivate::checkPermission("android.permission.POST_NOTIFICATIONS"); + //notif.waitForFinished(); + + if (notif.result() == QtAndroidPrivate::PermissionResult::Denied) + { + QtAndroidPrivate::requestPermission("android.permission.POST_NOTIFICATIONS"); + notif = QtAndroidPrivate::checkPermission("android.permission.POST_NOTIFICATIONS"); + //notif.waitForFinished(); + + if (notif.result() == QtAndroidPrivate::PermissionResult::Denied) + { + qWarning() << "POST_NOTIFICATIONS PERMISSION DENIED"; + status = false; + } + } + + return status; +} + +/* ************************************************************************** */ + +bool UtilsAndroid::checkPermission_bluetooth() +{ + bool status = false; + + // (up to) Android 11 / SDK 30 + // BLUETOOTH + // BLUETOOTH_ADMIN + + if (getSdkVersion() <= 30) + { + QFuture ble = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH"); + //ble.waitForFinished(); + + QFuture ble_admin = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_ADMIN"); + //ble_admin.waitForFinished(); + + status = (ble.result() == QtAndroidPrivate::PermissionResult::Authorized) && + (ble_admin.result() == QtAndroidPrivate::PermissionResult::Authorized); + } + + // (from) Android 12+ / SDK 31 + // BLUETOOTH_SCAN + // BLUETOOTH_CONNECT + + if (getSdkVersion() >= 31) + { + QFuture ble_scan = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_SCAN"); + //ble_scan.waitForFinished(); + + QFuture ble_connect = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_CONNECT"); + //ble_connect.waitForFinished(); + + status = (ble_scan.result() == QtAndroidPrivate::PermissionResult::Authorized) && + (ble_connect.result() == QtAndroidPrivate::PermissionResult::Authorized); + } + + return status; +} + +bool UtilsAndroid::getPermission_bluetooth() +{ + if (getSdkVersion() <= 30) + { + QFuture ble = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH"); + if (ble.result() == QtAndroidPrivate::PermissionResult::Denied) + { + QtAndroidPrivate::requestPermission("android.permission.BLUETOOTH"); + } + QFuture ble_admin = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_ADMIN"); + if (ble_admin.result() == QtAndroidPrivate::PermissionResult::Denied) + { + QtAndroidPrivate::requestPermission("android.permission.BLUETOOTH_ADMIN"); + } + } + + if (getSdkVersion() >= 31) + { + QFuture ble_scan = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_SCAN"); + if (ble_scan.result() == QtAndroidPrivate::PermissionResult::Denied) + { + QtAndroidPrivate::requestPermission("android.permission.BLUETOOTH_SCAN"); + } + QFuture ble_connect = QtAndroidPrivate::checkPermission("android.permission.BLUETOOTH_CONNECT"); + if (ble_connect.result() == QtAndroidPrivate::PermissionResult::Denied) + { + QtAndroidPrivate::requestPermission("android.permission.BLUETOOTH_CONNECT"); + } + } + + return checkPermission_bluetooth(); +} + +/* ************************************************************************** */ + bool UtilsAndroid::checkPermission_location() { QFuture loc = QtAndroidPrivate::checkPermission("android.permission.ACCESS_FINE_LOCATION");