Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime notifications permission #18239

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions WordPress/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- GCM all build types configuration -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.wordpress.android.ui.bloggingreminders

import android.Manifest
import android.content.Context
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
Expand All @@ -19,6 +21,8 @@ import org.wordpress.android.databinding.RecyclerViewPrimaryButtonBottomSheetBin
import org.wordpress.android.ui.bloggingprompts.onboarding.BloggingPromptsOnboardingDialogFragment
import org.wordpress.android.ui.bloggingprompts.onboarding.BloggingPromptsOnboardingDialogFragment.DialogType.INFORMATION
import org.wordpress.android.ui.utils.UiHelpers
import org.wordpress.android.util.PermissionUtils
import org.wordpress.android.util.WPPermissionUtils
import org.wordpress.android.util.extensions.disableAnimation
import org.wordpress.android.viewmodel.observeEvent
import javax.inject.Inject
Expand Down Expand Up @@ -61,8 +65,10 @@ class BloggingReminderBottomSheetFragment : BottomSheetDialogFragment() {
}
}
})
viewModel =
ViewModelProvider(requireActivity(), viewModelFactory).get(BloggingRemindersViewModel::class.java)
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[BloggingRemindersViewModel::class.java]

setPermissionState()

viewModel.uiState.observe(this@BloggingReminderBottomSheetFragment) { uiState ->
(contentRecyclerView.adapter as? BloggingRemindersAdapter)?.submitList(uiState?.uiItems ?: listOf())
if (uiState?.primaryButton != null) {
Expand All @@ -86,11 +92,10 @@ class BloggingReminderBottomSheetFragment : BottomSheetDialogFragment() {
BloggingReminderUtils.observeTimePicker(
viewModel.isTimePickerShowing,
viewLifecycleOwner,
BloggingReminderTimePicker.TAG,
{
requireActivity().supportFragmentManager
}
)
BloggingReminderTimePicker.TAG
) {
requireActivity().supportFragmentManager
}

savedInstanceState?.let { viewModel.restoreState(it) }

Expand All @@ -101,6 +106,55 @@ class BloggingReminderBottomSheetFragment : BottomSheetDialogFragment() {
}
}

override fun onResume() {
super.onResume()
val hasPermission = PermissionUtils.checkNotificationsPermission(activity)
if (hasPermission) {
viewModel.onPermissionGranted()
}
}

@Suppress("OVERRIDE_DEPRECATION")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
val granted = WPPermissionUtils.setPermissionListAsked(
requireActivity(),
requestCode,
permissions,
grantResults,
false
)
if (granted) {
viewModel.onPermissionGranted()
} else {
viewModel.onPermissionDenied()
}
}

private fun setPermissionState() {
val isAlwaysDenied = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
WPPermissionUtils.isPermissionAlwaysDenied(
requireActivity(),
Manifest.permission.POST_NOTIFICATIONS
)
viewModel.setPermissionState(PermissionUtils.checkNotificationsPermission(activity), isAlwaysDenied)

@Suppress("DEPRECATION")
viewModel.requestPermission.observeEvent(this@BloggingReminderBottomSheetFragment) { request: Boolean ->
if (request && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
WPPermissionUtils.NOTIFICATIONS_PERMISSION_REQUEST_CODE
)
}
}
viewModel.showDevicePermissionSettings
.observeEvent(this@BloggingReminderBottomSheetFragment) { show: Boolean ->
if (show) {
WPPermissionUtils.showNotificationsSettings(requireActivity())
}
}
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
viewModel.saveState(outState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.lifecycle.distinctUntilChanged
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import org.wordpress.android.R
import org.wordpress.android.fluxc.store.BloggingRemindersStore
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.modules.UI_THREAD
Expand All @@ -22,6 +23,7 @@ import org.wordpress.android.util.extensions.getSerializableCompat
import org.wordpress.android.util.merge
import org.wordpress.android.util.perform
import org.wordpress.android.viewmodel.Event
import org.wordpress.android.viewmodel.ResourceProvider
import org.wordpress.android.viewmodel.ScopedViewModel
import org.wordpress.android.workers.reminder.ReminderScheduler
import java.time.DayOfWeek
Expand All @@ -35,11 +37,13 @@ class BloggingRemindersViewModel @Inject constructor(
private val prologueBuilder: PrologueBuilder,
private val daySelectionBuilder: DaySelectionBuilder,
private val epilogueBuilder: EpilogueBuilder,
private val notificationsPermissionBuilder: NotificationsPermissionBuilder,
private val dayLabelUtils: DayLabelUtils,
private val analyticsTracker: BloggingRemindersAnalyticsTracker,
private val reminderScheduler: ReminderScheduler,
private val mapper: BloggingRemindersModelMapper,
private val siteStore: SiteStore
private val siteStore: SiteStore,
private val resourceProvider: ResourceProvider
) : ScopedViewModel(mainDispatcher) {
private val _isBottomSheetShowing = MutableLiveData<Event<Boolean>>()
val isBottomSheetShowing = _isBottomSheetShowing as LiveData<Event<Boolean>>
Expand All @@ -50,11 +54,19 @@ class BloggingRemindersViewModel @Inject constructor(
private val _showBloggingPromptHelpDialogVisible = MutableLiveData<Event<Boolean>>()
val showBloggingPromptHelpDialogVisible = _showBloggingPromptHelpDialogVisible as LiveData<Event<Boolean>>

private val _requestPermission = MutableLiveData<Event<Boolean>>()
val requestPermission = _requestPermission as LiveData<Event<Boolean>>

private val _showDevicePermissionSettings = MutableLiveData<Event<Boolean>>()
val showDevicePermissionSettings = _showDevicePermissionSettings as LiveData<Event<Boolean>>

private val _selectedScreen = MutableLiveData<Screen>()
private val selectedScreen = _selectedScreen.perform { onScreenChanged(it) }

private val _bloggingRemindersModel = MutableLiveData<BloggingRemindersUiModel>()
private val _isFirstTimeFlow = MutableLiveData<Boolean>()
private val _hasNotificationsPermission = MutableLiveData<Boolean>()
private val _notificationsPermissionAlwaysDenied = MutableLiveData<Boolean>()

val uiState: LiveData<UiState> = merge(
selectedScreen,
Expand All @@ -72,6 +84,9 @@ class BloggingRemindersViewModel @Inject constructor(
this::togglePromptSwitch,
this::showBloggingPromptDialog
)
Screen.NOTIFICATIONS_PERMISSION -> {
notificationsPermissionBuilder.buildUiItems(resourceProvider.getString(R.string.app_name))
}
Screen.EPILOGUE -> epilogueBuilder.buildUiItems(bloggingRemindersModel)
}
val primaryButton = when (screen) {
Expand All @@ -84,6 +99,7 @@ class BloggingRemindersViewModel @Inject constructor(
isFirstTimeFlow == true,
this::onSelectionButtonClick
)
Screen.NOTIFICATIONS_PERMISSION -> notificationsPermissionBuilder.buildPrimaryButton(showAppSettings)
Screen.EPILOGUE -> epilogueBuilder.buildPrimaryButton(finish)
}
UiState(uiItems, primaryButton)
Expand All @@ -103,6 +119,10 @@ class BloggingRemindersViewModel @Inject constructor(
_isBottomSheetShowing.value = Event(false)
}

private val showAppSettings: () -> Unit = {
_showDevicePermissionSettings.value = Event(true)
}

private fun onScreenChanged(screen: Screen) {
analyticsTracker.trackScreenShown(screen)
}
Expand Down Expand Up @@ -146,6 +166,11 @@ class BloggingRemindersViewModel @Inject constructor(
_bloggingRemindersModel.value = currentState.copy(enabledDays = enabledDays)
}

fun setPermissionState(hasNotificationsPermission: Boolean, notificationsPermissionAlwaysDenied: Boolean) {
_hasNotificationsPermission.value = hasNotificationsPermission
_notificationsPermissionAlwaysDenied.value = notificationsPermissionAlwaysDenied
}

fun selectTime() {
_isTimePickerShowing.value = Event(true)
}
Expand Down Expand Up @@ -185,6 +210,10 @@ class BloggingRemindersViewModel @Inject constructor(
)
val daysCount = bloggingRemindersModel.enabledDays.size
if (daysCount > 0) {
if (!checkPermission()) {
// There is no permission
return@launch
}
reminderScheduler.schedule(
bloggingRemindersModel.siteId,
bloggingRemindersModel.hour,
Expand All @@ -203,6 +232,22 @@ class BloggingRemindersViewModel @Inject constructor(
}
}

private fun checkPermission(): Boolean {
val hasPermission = _hasNotificationsPermission.value == true
val alwaysDenied = _notificationsPermissionAlwaysDenied.value == true
return when {
!hasPermission && alwaysDenied -> {
_selectedScreen.value = Screen.NOTIFICATIONS_PERMISSION
false
}
!hasPermission -> {
_requestPermission.value = Event(true)
false
}
else -> true // Already has permission
}
}

fun saveState(outState: Bundle) {
_selectedScreen.value?.let {
outState.putSerializable(SELECTED_SCREEN, it)
Expand Down Expand Up @@ -274,16 +319,34 @@ class BloggingRemindersViewModel @Inject constructor(
when (val screen = selectedScreen.value) {
Screen.PROLOGUE,
Screen.PROLOGUE_SETTINGS,
Screen.NOTIFICATIONS_PERMISSION,
Screen.SELECTION -> analyticsTracker.trackFlowDismissed(screen)
Screen.EPILOGUE -> analyticsTracker.trackFlowCompleted()
null -> Unit // Do nothing
}
}

fun onPermissionGranted() {
if (_hasNotificationsPermission.value == false) {
// Permission state is changed.
_hasNotificationsPermission.value = true
_notificationsPermissionAlwaysDenied.value = false
if (_selectedScreen.value == Screen.NOTIFICATIONS_PERMISSION) {
onSelectionButtonClick(_bloggingRemindersModel.value)
}
}
}

fun onPermissionDenied() {
_hasNotificationsPermission.value = false
_selectedScreen.value = Screen.NOTIFICATIONS_PERMISSION
}

enum class Screen(val trackingName: String) {
PROLOGUE("main"), // displayed after post is published
PROLOGUE_SETTINGS("main"), // displayed from Site Settings before showing cadence selector
SELECTION("day_picker"), // cadence selector
NOTIFICATIONS_PERMISSION("notifications_permission"),
EPILOGUE("all_set")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.wordpress.android.ui.bloggingreminders

import org.wordpress.android.R.drawable
import org.wordpress.android.R.string
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersItem.Caption
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersItem.EmphasizedText
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersItem.HighEmphasisText
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersItem.Illustration
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersItem.Title
import org.wordpress.android.ui.bloggingreminders.BloggingRemindersViewModel.UiState.PrimaryButton
import org.wordpress.android.ui.utils.ListItemInteraction
import org.wordpress.android.ui.utils.UiString
import org.wordpress.android.ui.utils.UiString.UiStringRes
import javax.inject.Inject

class NotificationsPermissionBuilder @Inject constructor() {
fun buildUiItems(appName: String): List<BloggingRemindersItem> {
val title = UiStringRes(string.blogging_reminders_notifications_permission_title)

val body = UiString.UiStringResWithParams(
string.blogging_reminders_notifications_permission_description,
UiString.UiStringText(appName)
)

return listOf(
Illustration(drawable.img_illustration_bell_yellow_96dp),
Title(title),
Caption(UiStringRes(string.blogging_reminders_notifications_permission_caption)),
HighEmphasisText(EmphasizedText(body, false))
)
}

fun buildPrimaryButton(onDone: () -> Unit): PrimaryButton {
return PrimaryButton(
UiStringRes(string.blogging_reminders_notifications_permission_primary_button),
enabled = true,
ListItemInteraction.create(onDone)
)
}
}
Loading