mirror of
https://github.com/home-assistant/android
synced 2024-10-02 22:34:46 +00:00
Android 13 notification changes + target (#2682)
* Request notification permission during onboarding - When using Android 13 or the minimal version, add a screen to onboarding to request to enable notifications on the device. This screen is based on iOS onboarding. When tapping Continue, on Android 13 this requests the system permission and/or on the minimal version this changes the websocket setting. - Compile with and target SDK 33 (targeting required to be able to request notification system permission on demand) * Add notification settings link in settings when off - Show a new setting in settings for the user to enable notifications for the Home Assistant app, either because the permission was denied (Android 13) or the user manually turned off all notifications (Android 8-12). - This new setting replaces the notification channels setting when everything is off; in this case there is no point managing individual channels. If at least one channel is on, the existing notification channels setting is shown instead. * Compose tweaks - Remove redundant content description for icon - Change to a simpler scroll modifier * Fix skipping notifications on minimal version - Ensure that the value is actually saved to the database * Post Bluetooth on/off commands as if invalid - When using Android 13, post notification commands to control Bluetooth on/off as a normal notification as if they were invalid, as the function has been deprecated for the app * Remove type that can be inferred
This commit is contained in:
parent
72681ca632
commit
4a9dd8ba04
|
@ -23,7 +23,7 @@ android {
|
|||
defaultConfig {
|
||||
applicationId = "io.homeassistant.companion.android"
|
||||
minSdk = 21
|
||||
targetSdk = 31
|
||||
targetSdk = 33
|
||||
|
||||
versionName = System.getenv("VERSION") ?: "LOCAL"
|
||||
versionCode = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1
|
||||
|
|
|
@ -175,13 +175,15 @@ class SettingsWearViewModel @Inject constructor(
|
|||
url: String,
|
||||
authCode: String,
|
||||
deviceName: String,
|
||||
deviceTrackingEnabled: Boolean
|
||||
deviceTrackingEnabled: Boolean,
|
||||
notificationsEnabled: Boolean
|
||||
) {
|
||||
val putDataRequest = PutDataMapRequest.create("/authenticate").run {
|
||||
dataMap.putString("URL", url)
|
||||
dataMap.putString("AuthCode", authCode)
|
||||
dataMap.putString("DeviceName", deviceName)
|
||||
dataMap.putBoolean("LocationTracking", deviceTrackingEnabled)
|
||||
dataMap.putBoolean("Notifications", notificationsEnabled)
|
||||
setUrgent()
|
||||
asPutDataRequest()
|
||||
}
|
||||
|
|
|
@ -74,15 +74,16 @@ class SettingsWearMainView : AppCompatActivity() {
|
|||
OnboardApp.Input(
|
||||
url = registerUrl,
|
||||
defaultDeviceName = currentNodes.firstOrNull()?.displayName ?: "unknown",
|
||||
locationTrackingPossible = false
|
||||
)
|
||||
locationTrackingPossible = false,
|
||||
notificationsPossible = false
|
||||
) // While notifications are technically possible, the app can't handle this for the Wear device
|
||||
)
|
||||
}
|
||||
|
||||
private fun onOnboardingComplete(result: OnboardApp.Output?) {
|
||||
if (result != null) {
|
||||
val (url, authCode, deviceName, deviceTrackingEnabled) = result
|
||||
settingsWearViewModel.sendAuthToWear(url, authCode, deviceName, deviceTrackingEnabled)
|
||||
val (url, authCode, deviceName, deviceTrackingEnabled, _) = result
|
||||
settingsWearViewModel.sendAuthToWear(url, authCode, deviceName, deviceTrackingEnabled, true)
|
||||
} else
|
||||
Log.e(TAG, "onOnboardingComplete: Activity result returned null intent data")
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.homeassistant.companion.android.launch
|
|||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
|
@ -18,9 +20,11 @@ import io.homeassistant.companion.android.common.data.integration.DeviceRegistra
|
|||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.url.UrlRepository
|
||||
import io.homeassistant.companion.android.database.sensor.SensorDao
|
||||
import io.homeassistant.companion.android.database.settings.WebsocketSetting
|
||||
import io.homeassistant.companion.android.onboarding.OnboardApp
|
||||
import io.homeassistant.companion.android.onboarding.getMessagingToken
|
||||
import io.homeassistant.companion.android.sensors.LocationSensorManager
|
||||
import io.homeassistant.companion.android.settings.SettingViewModel
|
||||
import io.homeassistant.companion.android.webview.WebViewActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -56,6 +60,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private val settingViewModel: SettingViewModel by viewModels()
|
||||
|
||||
private val registerActivityResult = registerForActivityResult(
|
||||
OnboardApp(),
|
||||
this::onOnboardingComplete
|
||||
|
@ -95,7 +101,7 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
private fun onOnboardingComplete(result: OnboardApp.Output?) {
|
||||
mainScope.launch {
|
||||
if (result != null) {
|
||||
val (url, authCode, deviceName, deviceTrackingEnabled) = result
|
||||
val (url, authCode, deviceName, deviceTrackingEnabled, notificationsEnabled) = result
|
||||
val messagingToken = getMessagingToken()
|
||||
if (messagingToken.isBlank() && BuildConfig.FLAVOR == "full") {
|
||||
AlertDialog.Builder(this@LaunchActivity)
|
||||
|
@ -108,7 +114,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
authCode,
|
||||
deviceName,
|
||||
messagingToken,
|
||||
deviceTrackingEnabled
|
||||
deviceTrackingEnabled,
|
||||
notificationsEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +126,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
authCode,
|
||||
deviceName,
|
||||
messagingToken,
|
||||
deviceTrackingEnabled
|
||||
deviceTrackingEnabled,
|
||||
notificationsEnabled
|
||||
)
|
||||
}
|
||||
} else
|
||||
|
@ -132,7 +140,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
authCode: String,
|
||||
deviceName: String,
|
||||
messagingToken: String,
|
||||
deviceTrackingEnabled: Boolean
|
||||
deviceTrackingEnabled: Boolean,
|
||||
notificationsEnabled: Boolean
|
||||
) {
|
||||
try {
|
||||
urlRepository.saveUrl(url)
|
||||
|
@ -176,6 +185,7 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
return
|
||||
}
|
||||
setLocationTracking(deviceTrackingEnabled)
|
||||
setNotifications(notificationsEnabled)
|
||||
displayWebview()
|
||||
}
|
||||
|
||||
|
@ -189,4 +199,16 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
|
|||
enabled = enabled
|
||||
)
|
||||
}
|
||||
|
||||
private fun setNotifications(enabled: Boolean) {
|
||||
// Full: this only refers to the system permission on Android 13+ so no changes are necessary.
|
||||
// Minimal: change persistent connection setting to reflect preference.
|
||||
if (BuildConfig.FLAVOR != "full") {
|
||||
settingViewModel.getSetting(0) // Required to create initial value
|
||||
settingViewModel.updateWebsocketSetting(
|
||||
0,
|
||||
if (enabled) WebsocketSetting.ALWAYS else WebsocketSetting.NEVER
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -386,7 +386,11 @@ class MessagingManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
COMMAND_BLUETOOTH -> {
|
||||
if (!jsonData[COMMAND].isNullOrEmpty() && jsonData[COMMAND] in ENABLE_COMMANDS)
|
||||
if (
|
||||
!jsonData[COMMAND].isNullOrEmpty() &&
|
||||
jsonData[COMMAND] in ENABLE_COMMANDS &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|
||||
)
|
||||
handleDeviceCommands(jsonData)
|
||||
else {
|
||||
mainScope.launch {
|
||||
|
@ -770,9 +774,10 @@ class MessagingManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
if (command == TURN_OFF)
|
||||
bluetoothAdapter?.disable()
|
||||
if (command == TURN_ON)
|
||||
else if (command == TURN_ON)
|
||||
bluetoothAdapter?.enable()
|
||||
}
|
||||
COMMAND_BLE_TRANSMITTER -> {
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.homeassistant.companion.android.onboarding
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.icu.util.Output
|
||||
import android.os.Build
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
|
@ -13,25 +12,29 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
|
|||
private const val EXTRA_URL = "extra_url"
|
||||
private const val EXTRA_DEFAULT_DEVICE_NAME = "extra_default_device_name"
|
||||
private const val EXTRA_LOCATION_TRACKING_POSSIBLE = "location_tracking_possible"
|
||||
private const val EXTRA_NOTIFICATIONS_POSSIBLE = "notifications_possible"
|
||||
|
||||
fun parseInput(intent: Intent): Input = Input(
|
||||
url = intent.getStringExtra(EXTRA_URL),
|
||||
defaultDeviceName = intent.getStringExtra(EXTRA_DEFAULT_DEVICE_NAME) ?: Build.MODEL,
|
||||
locationTrackingPossible = intent.getBooleanExtra(EXTRA_LOCATION_TRACKING_POSSIBLE, false),
|
||||
notificationsPossible = intent.getBooleanExtra(EXTRA_NOTIFICATIONS_POSSIBLE, true)
|
||||
)
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val url: String? = null,
|
||||
val defaultDeviceName: String = Build.MODEL,
|
||||
val locationTrackingPossible: Boolean = BuildConfig.FLAVOR == "full"
|
||||
val locationTrackingPossible: Boolean = BuildConfig.FLAVOR == "full",
|
||||
val notificationsPossible: Boolean = true
|
||||
)
|
||||
|
||||
data class Output(
|
||||
val url: String,
|
||||
val authCode: String,
|
||||
val deviceName: String,
|
||||
val deviceTrackingEnabled: Boolean
|
||||
val deviceTrackingEnabled: Boolean,
|
||||
val notificationsEnabled: Boolean
|
||||
) {
|
||||
fun toIntent(): Intent {
|
||||
return Intent().apply {
|
||||
|
@ -39,6 +42,7 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
|
|||
putExtra("AuthCode", authCode)
|
||||
putExtra("DeviceName", deviceName)
|
||||
putExtra("LocationTracking", deviceTrackingEnabled)
|
||||
putExtra("Notifications", notificationsEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +52,7 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
|
|||
putExtra(EXTRA_URL, input.url)
|
||||
putExtra(EXTRA_DEFAULT_DEVICE_NAME, input.defaultDeviceName)
|
||||
putExtra(EXTRA_LOCATION_TRACKING_POSSIBLE, input.locationTrackingPossible)
|
||||
putExtra(EXTRA_NOTIFICATIONS_POSSIBLE, input.notificationsPossible)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,11 +65,13 @@ class OnboardApp : ActivityResultContract<OnboardApp.Input, OnboardApp.Output?>(
|
|||
val authCode = intent.getStringExtra("AuthCode").toString()
|
||||
val deviceName = intent.getStringExtra("DeviceName").toString()
|
||||
val deviceTrackingEnabled = intent.getBooleanExtra("LocationTracking", false)
|
||||
val notificationsEnabled = intent.getBooleanExtra("Notifications", true)
|
||||
return Output(
|
||||
url = url,
|
||||
authCode = authCode,
|
||||
deviceName = deviceName,
|
||||
deviceTrackingEnabled = deviceTrackingEnabled
|
||||
deviceTrackingEnabled = deviceTrackingEnabled,
|
||||
notificationsEnabled = notificationsEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import android.os.Bundle
|
|||
import android.view.KeyEvent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.fragment.app.commit
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.onboarding.authentication.AuthenticationFragment
|
||||
import io.homeassistant.companion.android.onboarding.discovery.DiscoveryFragment
|
||||
|
@ -29,6 +31,14 @@ class OnboardingActivity : AppCompatActivity() {
|
|||
val input = OnboardApp.parseInput(intent)
|
||||
viewModel.deviceName.value = input.defaultDeviceName
|
||||
viewModel.locationTrackingPossible.value = input.locationTrackingPossible
|
||||
viewModel.notificationsPossible.value = input.notificationsPossible
|
||||
viewModel.notificationsEnabled = if (input.notificationsPossible) {
|
||||
BuildConfig.FLAVOR == "full" &&
|
||||
(
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
NotificationManagerCompat.from(this).areNotificationsEnabled()
|
||||
)
|
||||
} else false
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager.commit {
|
||||
|
|
|
@ -52,6 +52,8 @@ class OnboardingViewModel @Inject constructor(
|
|||
val deviceName = mutableStateOf("")
|
||||
val locationTrackingPossible = mutableStateOf(false)
|
||||
var locationTrackingEnabled by mutableStateOf(false)
|
||||
val notificationsPossible = mutableStateOf(true)
|
||||
var notificationsEnabled by mutableStateOf(false)
|
||||
|
||||
fun onManualUrlUpdated(url: String) {
|
||||
manualUrl.value = url
|
||||
|
@ -80,6 +82,18 @@ class OnboardingViewModel @Inject constructor(
|
|||
locationTrackingEnabled = enabled
|
||||
}
|
||||
|
||||
fun setNotifications(enabled: Boolean) {
|
||||
notificationsEnabled = enabled
|
||||
}
|
||||
|
||||
fun getOutput() = OnboardApp.Output(
|
||||
url = manualUrl.value,
|
||||
authCode = authCode,
|
||||
deviceName = deviceName.value,
|
||||
deviceTrackingEnabled = locationTrackingEnabled,
|
||||
notificationsEnabled = notificationsEnabled
|
||||
)
|
||||
|
||||
override fun onCleared() {
|
||||
_homeAssistantSearcher.stopSearch()
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
|
||||
import io.homeassistant.companion.android.onboarding.OnboardApp
|
||||
import io.homeassistant.companion.android.onboarding.OnboardingViewModel
|
||||
import io.homeassistant.companion.android.onboarding.notifications.NotificationPermissionFragment
|
||||
import io.homeassistant.companion.android.sensors.LocationSensorManager
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
|
@ -151,14 +152,16 @@ class MobileAppIntegrationFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun onComplete() {
|
||||
val retData = OnboardApp.Output(
|
||||
url = viewModel.manualUrl.value,
|
||||
authCode = viewModel.authCode,
|
||||
deviceName = viewModel.deviceName.value,
|
||||
deviceTrackingEnabled = viewModel.locationTrackingEnabled
|
||||
)
|
||||
activity?.setResult(Activity.RESULT_OK, retData.toIntent())
|
||||
activity?.finish()
|
||||
if (viewModel.notificationsPossible.value != viewModel.notificationsEnabled) {
|
||||
parentFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.content, NotificationPermissionFragment::class.java, null)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
} else { // Complete onboarding
|
||||
activity?.setResult(Activity.RESULT_OK, viewModel.getOutput().toIntent())
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPrivacyPolicy() {
|
||||
|
|
|
@ -88,7 +88,7 @@ fun MobileAppIntegrationView(
|
|||
onClick = onFinishClicked,
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
) {
|
||||
Text(stringResource(id = commonR.string.finish))
|
||||
Text(stringResource(id = commonR.string.continue_connect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package io.homeassistant.companion.android.onboarding.notifications
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import io.homeassistant.companion.android.onboarding.OnboardingViewModel
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
class NotificationPermissionFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NotificationPermFrag"
|
||||
}
|
||||
|
||||
private val viewModel by activityViewModels<OnboardingViewModel>()
|
||||
|
||||
private val permissionsRequest = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
Log.i(TAG, "Notification permission was ${if (isGranted) "granted" else "not granted"}")
|
||||
viewModel.setNotifications(isGranted)
|
||||
|
||||
if (isGranted) onComplete()
|
||||
else showPermissionDeniedDialog()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
MdcTheme {
|
||||
NotificationPermissionView(
|
||||
onSetNotificationsEnabled = ::setNotifications
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNotifications(enabled: Boolean) {
|
||||
if (enabled) {
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
!NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
|
||||
) {
|
||||
permissionsRequest.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
viewModel.setNotifications(true)
|
||||
onComplete()
|
||||
}
|
||||
} else {
|
||||
viewModel.setNotifications(false)
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPermissionDeniedDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(commonR.string.onboarding_notifications_denied)
|
||||
.setPositiveButton(commonR.string.continue_connect) { _, _ ->
|
||||
onComplete()
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onComplete() {
|
||||
activity?.setResult(Activity.RESULT_OK, viewModel.getOutput().toIntent())
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package io.homeassistant.companion.android.onboarding.notifications
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.colorResource
|
||||
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 com.google.android.material.composethemeadapter.MdcTheme
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.IIcon
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun NotificationPermissionView(
|
||||
onSetNotificationsEnabled: (Boolean) -> Unit
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 32.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Notifications,
|
||||
contentDescription = null,
|
||||
tint = colorResource(id = commonR.color.colorAccent),
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = commonR.string.notifications),
|
||||
style = MaterialTheme.typography.h5,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = commonR.string.onboarding_notifications_subtitle),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 48.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
NotificationPermissionBullet(
|
||||
icon = CommunityMaterial.Icon.cmd_alert_decagram,
|
||||
text = stringResource(id = commonR.string.onboarding_notifications_bullet_alert)
|
||||
)
|
||||
NotificationPermissionBullet(
|
||||
icon = CommunityMaterial.Icon3.cmd_text,
|
||||
text = stringResource(id = commonR.string.onboarding_notifications_bullet_commands)
|
||||
)
|
||||
}
|
||||
Row(modifier = Modifier.padding(top = 16.dp)) {
|
||||
TextButton(onClick = { onSetNotificationsEnabled(false) }) {
|
||||
Text(stringResource(id = commonR.string.skip))
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Button(onClick = { onSetNotificationsEnabled(true) }) {
|
||||
Text(stringResource(id = commonR.string.continue_connect))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NotificationPermissionBullet(
|
||||
icon: IIcon,
|
||||
text: String
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 12.dp)
|
||||
) {
|
||||
Image(
|
||||
asset = icon,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Composable
|
||||
fun NotificationPermissionViewPreview() {
|
||||
MdcTheme {
|
||||
NotificationPermissionView(
|
||||
onSetNotificationsEnabled = {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
@ -73,6 +74,10 @@ class SettingsFragment constructor(
|
|||
updateBackgroundAccessPref()
|
||||
}
|
||||
|
||||
private val requestNotificationPermissionResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
updateNotificationChannelPrefs()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
presenter.init(this)
|
||||
|
||||
|
@ -245,10 +250,17 @@ class SettingsFragment constructor(
|
|||
}
|
||||
}
|
||||
|
||||
updateNotificationChannelPrefs()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
findPreference<Preference>("notification_permission")?.let {
|
||||
it.setOnPreferenceClickListener {
|
||||
openNotificationSettings()
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("notification_channels")?.let { pref ->
|
||||
val uiManager = requireContext().getSystemService<UiModeManager>()
|
||||
pref.isVisible = uiManager?.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION
|
||||
pref.setOnPreferenceClickListener {
|
||||
parentFragmentManager
|
||||
.beginTransaction()
|
||||
|
@ -517,6 +529,23 @@ class SettingsFragment constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateNotificationChannelPrefs() {
|
||||
val notificationsEnabled =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()
|
||||
|
||||
findPreference<Preference>("notification_permission")?.let {
|
||||
it.isVisible = !notificationsEnabled
|
||||
}
|
||||
findPreference<Preference>("notification_channels")?.let {
|
||||
val uiManager = requireContext().getSystemService<UiModeManager>()
|
||||
it.isVisible =
|
||||
notificationsEnabled &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
uiManager?.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
private fun requestBackgroundAccess() {
|
||||
if (!isIgnoringBatteryOptimizations()) {
|
||||
|
@ -529,6 +558,16 @@ class SettingsFragment constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun openNotificationSettings() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
requestNotificationPermissionResult.launch(
|
||||
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndRequestPermissions(permissions: Array<String>, requestCode: Int, requestPermissions: Array<String>? = null, forceRequest: Boolean = false): Boolean {
|
||||
val permissionsNeeded = mutableListOf<String>()
|
||||
for (permission in permissions) {
|
||||
|
|
5
app/src/main/res/drawable/ic_notification_off.xml
Normal file
5
app/src/main/res/drawable/ic_notification_off.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="@color/colorAccent"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z"/>
|
||||
</vector>
|
|
@ -129,6 +129,12 @@
|
|||
android:title="@string/notifications"
|
||||
android:key="notifications"
|
||||
app:isPreferenceVisible="false">
|
||||
<Preference
|
||||
android:key="notification_permission"
|
||||
android:title="@string/notification_permission"
|
||||
android:summary="@string/notification_permission_summary"
|
||||
app:isPreferenceVisible="false"
|
||||
android:icon="@drawable/ic_notification_off" />
|
||||
<Preference
|
||||
android:key="notification_channels"
|
||||
android:title="@string/notification_channels"
|
||||
|
|
|
@ -390,6 +390,10 @@
|
|||
<string name="notification_source">Notification Source</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="onboarding_notifications_denied">If you want to enable notifications in the future, you can change this in Settings.</string>
|
||||
<string name="onboarding_notifications_bullet_alert">Get alerted from notifications</string>
|
||||
<string name="onboarding_notifications_bullet_commands">Send commands to your device</string>
|
||||
<string name="onboarding_notifications_subtitle">Enable notifications to create a notify service for your device</string>
|
||||
<string name="other_settings">Other Settings</string>
|
||||
<string name="other">Other</string>
|
||||
<string name="password">Password</string>
|
||||
|
@ -852,6 +856,8 @@
|
|||
<string name="websocket_notification_issues">Persistent Connection Issues</string>
|
||||
<string name="notification_channels">Notification Channels</string>
|
||||
<string name="notification_channels_summary">Manage all notification channels configured on the device. Channels control the behavior of its notifications including visibility and sound.</string>
|
||||
<string name="notification_permission">Notification Permission</string>
|
||||
<string name="notification_permission_summary">Home Assistant does not have permission to send you notifications. Click here to enable sending notifications.</string>
|
||||
<string name="info">Information</string>
|
||||
<string name="show_changelog">Show Change Log</string>
|
||||
<string name="show_changelog_summary">Show the change log dialog from when the app was updated</string>
|
||||
|
|
|
@ -114,6 +114,7 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
|
|||
val authCode = dataMap.getString("AuthCode", "")
|
||||
val deviceName = dataMap.getString("DeviceName")
|
||||
val deviceTrackingEnabled = dataMap.getBoolean("LocationTracking")
|
||||
val notificationsEnabled = dataMap.getString("Notifications")
|
||||
|
||||
urlRepository.saveUrl(url)
|
||||
authenticationRepository.registerAuthorizationCode(authCode)
|
||||
|
|
Loading…
Reference in a new issue