diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 937862c9a75c..22a20551a3e3 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,9 +1,10 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 22.2 -[***] [internal] Adds media permissions support for Android 13 [https://github.com/wordpress-mobile/WordPress-Android/pull/18183] ----- - +* [*] Adds runtime notifications permission [https://github.com/wordpress-mobile/WordPress-Android/pull/18239] +* [**] [internal] Adds media permissions support for Android 13 [https://github.com/wordpress-mobile/WordPress-Android/pull/18183] +----- 22.1 ----- diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 6d3426300cfa..bb8bd0357ae0 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" /> + diff --git a/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingReminderBottomSheetFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingReminderBottomSheetFragment.kt index 46c150846f4c..7356d5aff0d3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingReminderBottomSheetFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingReminderBottomSheetFragment.kt @@ -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 @@ -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 @@ -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) { @@ -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) } @@ -101,6 +106,60 @@ 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, grantResults: IntArray) { + val granted = WPPermissionUtils.setPermissionListAsked( + requireActivity(), + requestCode, + permissions, + grantResults, + false + ) + if (granted) { + viewModel.onPermissionGranted() + } else { + val isAlwaysDenied = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + WPPermissionUtils.isPermissionAlwaysDenied( + requireActivity(), + Manifest.permission.POST_NOTIFICATIONS + ) + viewModel.onPermissionDenied(isAlwaysDenied) + } + } + + 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) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingRemindersViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingRemindersViewModel.kt index 9ee9631943b7..3378085a404e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingRemindersViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/BloggingRemindersViewModel.kt @@ -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 @@ -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 @@ -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>() val isBottomSheetShowing = _isBottomSheetShowing as LiveData> @@ -50,11 +54,19 @@ class BloggingRemindersViewModel @Inject constructor( private val _showBloggingPromptHelpDialogVisible = MutableLiveData>() val showBloggingPromptHelpDialogVisible = _showBloggingPromptHelpDialogVisible as LiveData> + private val _requestPermission = MutableLiveData>() + val requestPermission = _requestPermission as LiveData> + + private val _showDevicePermissionSettings = MutableLiveData>() + val showDevicePermissionSettings = _showDevicePermissionSettings as LiveData> + private val _selectedScreen = MutableLiveData() private val selectedScreen = _selectedScreen.perform { onScreenChanged(it) } private val _bloggingRemindersModel = MutableLiveData() private val _isFirstTimeFlow = MutableLiveData() + private var hasNotificationsPermissionState = false + private var notificationsPermissionAlwaysDeniedState = false val uiState: LiveData = merge( selectedScreen, @@ -72,6 +84,12 @@ class BloggingRemindersViewModel @Inject constructor( this::togglePromptSwitch, this::showBloggingPromptDialog ) + Screen.NOTIFICATIONS_PERMISSION -> { + notificationsPermissionBuilder.buildUiItems( + appName = resourceProvider.getString(R.string.app_name), + showAppSettingsGuide = notificationsPermissionAlwaysDeniedState + ) + } Screen.EPILOGUE -> epilogueBuilder.buildUiItems(bloggingRemindersModel) } val primaryButton = when (screen) { @@ -84,6 +102,9 @@ class BloggingRemindersViewModel @Inject constructor( isFirstTimeFlow == true, this::onSelectionButtonClick ) + Screen.NOTIFICATIONS_PERMISSION -> { + notificationsPermissionBuilder.buildPrimaryButton(onPermissionButtonTapped) + } Screen.EPILOGUE -> epilogueBuilder.buildPrimaryButton(finish) } UiState(uiItems, primaryButton) @@ -103,6 +124,14 @@ class BloggingRemindersViewModel @Inject constructor( _isBottomSheetShowing.value = Event(false) } + private val onPermissionButtonTapped: () -> Unit = { + if (notificationsPermissionAlwaysDeniedState) { + _showDevicePermissionSettings.value = Event(true) + } else { + _requestPermission.value = Event(true) + } + } + private fun onScreenChanged(screen: Screen) { analyticsTracker.trackScreenShown(screen) } @@ -135,7 +164,7 @@ class BloggingRemindersViewModel @Inject constructor( } } - fun selectDay(day: DayOfWeek) { + private fun selectDay(day: DayOfWeek) { val currentState = _bloggingRemindersModel.value!! val enabledDays = currentState.enabledDays.toMutableSet() if (enabledDays.contains(day)) { @@ -146,7 +175,12 @@ class BloggingRemindersViewModel @Inject constructor( _bloggingRemindersModel.value = currentState.copy(enabledDays = enabledDays) } - fun selectTime() { + fun setPermissionState(hasNotificationsPermission: Boolean, notificationsPermissionAlwaysDenied: Boolean) { + hasNotificationsPermissionState = hasNotificationsPermission + notificationsPermissionAlwaysDeniedState = notificationsPermissionAlwaysDenied + } + + private fun selectTime() { _isTimePickerShowing.value = Event(true) } @@ -178,12 +212,16 @@ class BloggingRemindersViewModel @Inject constructor( analyticsTracker.trackPrimaryButtonPressed(Screen.SELECTION) if (bloggingRemindersModel != null) { launch { + val daysCount = bloggingRemindersModel.enabledDays.size + if (!checkPermission()) { + // There is no permission + return@launch + } bloggingRemindersStore.updateBloggingReminders( mapper.toDomainModel( bloggingRemindersModel ) ) - val daysCount = bloggingRemindersModel.enabledDays.size if (daysCount > 0) { reminderScheduler.schedule( bloggingRemindersModel.siteId, @@ -203,6 +241,20 @@ class BloggingRemindersViewModel @Inject constructor( } } + private fun checkPermission(): Boolean { + return when { + !hasNotificationsPermissionState && notificationsPermissionAlwaysDeniedState -> { + _selectedScreen.value = Screen.NOTIFICATIONS_PERMISSION + false + } + !hasNotificationsPermissionState -> { + _requestPermission.value = Event(true) + false + } + else -> true // Already has permission + } + } + fun saveState(outState: Bundle) { _selectedScreen.value?.let { outState.putSerializable(SELECTED_SCREEN, it) @@ -274,16 +326,37 @@ 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 (!hasNotificationsPermissionState) { + // Permission state is changed. + hasNotificationsPermissionState = true + notificationsPermissionAlwaysDeniedState = false + + if (_selectedScreen.value == Screen.NOTIFICATIONS_PERMISSION) { + onSelectionButtonClick(_bloggingRemindersModel.value) + } + } + } + + fun onPermissionDenied(isAlwaysDenied: Boolean) { + hasNotificationsPermissionState = false + notificationsPermissionAlwaysDeniedState = isAlwaysDenied + + _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") } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/NotificationsPermissionBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/NotificationsPermissionBuilder.kt new file mode 100644 index 000000000000..e008d9447cd7 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/bloggingreminders/NotificationsPermissionBuilder.kt @@ -0,0 +1,43 @@ +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, showAppSettingsGuide: Boolean): List { + val title = UiStringRes(string.blogging_reminders_notifications_permission_title) + + val body = UiString.UiStringResWithParams( + string.blogging_reminders_notifications_permission_description, + UiString.UiStringText(appName) + ) + + val uiItems = mutableListOf( + Illustration(drawable.img_illustration_bell_yellow_96dp), + Title(title), + Caption(UiStringRes(string.blogging_reminders_notifications_permission_caption)) + ) + if (showAppSettingsGuide) { + uiItems.add(HighEmphasisText(EmphasizedText(body, false))) + } + return uiItems + } + + fun buildPrimaryButton(onDone: () -> Unit): PrimaryButton { + return PrimaryButton( + UiStringRes(string.blogging_reminders_notifications_permission_primary_button), + enabled = true, + ListItemInteraction.create(onDone) + ) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.kt index 4fa1055660b9..15a310182506 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.kt @@ -2,8 +2,10 @@ package org.wordpress.android.ui.notifications +import android.Manifest import android.app.Activity import android.content.Intent +import android.os.Build import android.os.Bundle import android.text.TextUtils import android.view.Menu @@ -13,6 +15,7 @@ import android.view.View import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.core.text.HtmlCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.recyclerview.widget.RecyclerView @@ -30,6 +33,7 @@ import org.wordpress.android.analytics.AnalyticsTracker.NOTIFICATIONS_SELECTED_F import org.wordpress.android.analytics.AnalyticsTracker.Stat.NOTIFICATION_TAPPED_SEGMENTED_CONTROL import org.wordpress.android.databinding.NotificationsListFragmentBinding import org.wordpress.android.fluxc.store.AccountStore +import org.wordpress.android.models.JetpackPoweredScreen import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.JetpackConnectionSource.NOTIFICATIONS import org.wordpress.android.ui.JetpackConnectionWebViewActivity @@ -54,8 +58,10 @@ import org.wordpress.android.ui.notifications.services.NotificationsUpdateServic import org.wordpress.android.ui.stats.StatsConnectJetpackActivity import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.JetpackBrandingUtils -import org.wordpress.android.models.JetpackPoweredScreen import org.wordpress.android.util.NetworkUtils +import org.wordpress.android.util.PermissionUtils +import org.wordpress.android.util.WPPermissionUtils +import org.wordpress.android.util.WPPermissionUtils.NOTIFICATIONS_PERMISSION_REQUEST_CODE import org.wordpress.android.util.WPUrlUtils import org.wordpress.android.util.extensions.setLiftOnScrollTargetViewIdAndRequestLayout import org.wordpress.android.viewmodel.observeEvent @@ -174,6 +180,7 @@ class NotificationsListFragment : Fragment(R.layout.notifications_list_fragment) } } setSelectedTab(lastTabPosition) + setNotificationPermissionWarning() } viewModel.onResume() } @@ -202,6 +209,57 @@ class NotificationsListFragment : Fragment(R.layout.notifications_list_fragment) tabLayout.getTabAt(lastTabPosition)?.select() } + private fun NotificationsListFragmentBinding.setNotificationPermissionWarning() { + val hasPermission = PermissionUtils.checkNotificationsPermission(activity) + if (hasPermission) { + // If the permissions is granted, we should reset the state of the warning. Because the permission may be + // disabled later, then we should be able to show the warning again to inform the user. + viewModel.resetNotificationsPermissionWarningDismissState() + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || hasPermission || + viewModel.isNotificationsPermissionsWarningDismissed + ) { + // If the user dismissed the warning, don't show it again. + notificationPermissionWarning.isVisible = false + } else { + notificationPermissionWarning.isVisible = true + notificationPermissionWarning.setOnClickListener { + val isAlwaysDenied = WPPermissionUtils.isPermissionAlwaysDenied( + requireActivity(), + Manifest.permission.POST_NOTIFICATIONS + ) + if (isAlwaysDenied) { + NotificationsPermissionBottomSheetFragment().show( + parentFragmentManager, + NotificationsPermissionBottomSheetFragment.TAG + ) + } else { + requestPermissions( + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + NOTIFICATIONS_PERMISSION_REQUEST_CODE + ) + } + } + permissionDismissButton.setOnClickListener { + notificationPermissionWarning.isVisible = false + viewModel.onNotificationsPermissionWarningDismissed() + } + } + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + WPPermissionUtils.setPermissionListAsked( + requireActivity(), + requestCode, + permissions, + grantResults, + false + ) + viewModel.resetNotificationsPermissionWarningDismissState() + } + private fun NotificationsListFragmentBinding.showConnectJetpackView() { clearToolbarScrollFlags() jetpackSetup.setOnClickListener { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListViewModel.kt index 12d853754376..f9f8b14113a9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListViewModel.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineDispatcher import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackOverlayConnectedFeature.NOTIFICATIONS +import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.JetpackBrandingUtils import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ScopedViewModel @@ -16,6 +17,7 @@ import javax.inject.Named @HiltViewModel class NotificationsListViewModel @Inject constructor( @Named(UI_THREAD) mainDispatcher: CoroutineDispatcher, + private val appPrefsWrapper: AppPrefsWrapper, private val jetpackBrandingUtils: JetpackBrandingUtils, private val jetpackFeatureRemovalOverlayUtil: JetpackFeatureRemovalOverlayUtil @@ -26,6 +28,9 @@ class NotificationsListViewModel @Inject constructor( private val _showJetpackOverlay = MutableLiveData>() val showJetpackOverlay: LiveData> = _showJetpackOverlay + val isNotificationsPermissionsWarningDismissed + get() = appPrefsWrapper.notificationPermissionsWarningDismissed + init { if (jetpackBrandingUtils.shouldShowJetpackPoweredBottomSheet()) showJetpackPoweredBottomSheet() } @@ -42,4 +47,12 @@ class NotificationsListViewModel @Inject constructor( private fun showJetpackOverlay() { _showJetpackOverlay.value = Event(true) } + + fun onNotificationsPermissionWarningDismissed() { + appPrefsWrapper.notificationPermissionsWarningDismissed = true + } + + fun resetNotificationsPermissionWarningDismissState() { + appPrefsWrapper.notificationPermissionsWarningDismissed = false + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsPermissionBottomSheetFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsPermissionBottomSheetFragment.kt new file mode 100644 index 000000000000..9ba6f79f6cf2 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsPermissionBottomSheetFragment.kt @@ -0,0 +1,39 @@ +package org.wordpress.android.ui.notifications + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.wordpress.android.R +import org.wordpress.android.databinding.NotificationsPermissionBottomSheetBinding +import org.wordpress.android.util.WPPermissionUtils + +class NotificationsPermissionBottomSheetFragment : BottomSheetDialogFragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.notifications_permission_bottom_sheet, container) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + (dialog as? BottomSheetDialog)?.behavior?.state = BottomSheetBehavior.STATE_EXPANDED + with(NotificationsPermissionBottomSheetBinding.bind(view)) { + val appName = getString(R.string.app_name) + description.text = getString(R.string.notifications_permission_bottom_sheet_description_2, appName) + + primaryButton.setOnClickListener { + WPPermissionUtils.showNotificationsSettings(requireActivity()) + dismiss() + } + } + } + + companion object { + const val TAG = "NOTIFICATIONS_PERMISSION_BOTTOM_SHEET_FRAGMENT" + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java index 9258be0d23f9..eaf7ec019ec3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java @@ -190,6 +190,7 @@ public enum DeletablePrefKey implements PrefKey { // Jetpack Individual Plugin overlay for WordPress app WP_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY_SHOWN_COUNT, WP_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY_LAST_SHOWN_TIMESTAMP, + NOTIFICATIONS_PERMISSION_WARNING_DISMISSED, } /** @@ -238,6 +239,8 @@ public enum UndeletablePrefKey implements PrefKey { ASKED_PERMISSION_AUDIO_READ, ASKED_PERMISSION_CAMERA, + ASKED_PERMISSION_NOTIFICATIONS, + // Updated after WP.com themes have been fetched LAST_WP_COM_THEMES_SYNC, @@ -1649,4 +1652,12 @@ public static long getWPJetpackIndividualPluginOverlayLastShownTimestamp() { public static void setWPJetpackIndividualPluginOverlayLastShownTimestamp(long timestamp) { setLong(DeletablePrefKey.WP_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY_LAST_SHOWN_TIMESTAMP, timestamp); } + + public static boolean getNotificationsPermissionsWarningDismissed() { + return getBoolean(DeletablePrefKey.NOTIFICATIONS_PERMISSION_WARNING_DISMISSED, false); + } + + public static void setNotificationsPermissionWarningDismissed(boolean dismissed) { + setBoolean(DeletablePrefKey.NOTIFICATIONS_PERMISSION_WARNING_DISMISSED, dismissed); + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt index 377e3b84f5ef..c239d7edd8a5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt @@ -93,6 +93,10 @@ class AppPrefsWrapper @Inject constructor() { get() = AppPrefs.getWPJetpackIndividualPluginOverlayLastShownTimestamp() set(timestamp) = AppPrefs.setWPJetpackIndividualPluginOverlayLastShownTimestamp(timestamp) + var notificationPermissionsWarningDismissed: Boolean + get() = AppPrefs.getNotificationsPermissionsWarningDismissed() + set(dismissed) = AppPrefs.setNotificationsPermissionWarningDismissed(dismissed) + fun getAppWidgetSiteId(appWidgetId: Int) = AppPrefs.getStatsWidgetSelectedSiteId(appWidgetId) fun setAppWidgetSiteId(siteId: Long, appWidgetId: Int) = AppPrefs.setStatsWidgetSelectedSiteId(siteId, appWidgetId) fun removeAppWidgetSiteId(appWidgetId: Int) = AppPrefs.removeStatsWidgetSelectedSiteId(appWidgetId) diff --git a/WordPress/src/main/java/org/wordpress/android/util/WPPermissionUtils.java b/WordPress/src/main/java/org/wordpress/android/util/WPPermissionUtils.java index 9bdbfd613a71..ca49f13b91b2 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/WPPermissionUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/WPPermissionUtils.java @@ -7,6 +7,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.provider.Settings; import androidx.annotation.NonNull; @@ -37,6 +39,8 @@ public class WPPermissionUtils { public static final int EDITOR_DRAG_DROP_PERMISSION_REQUEST_CODE = 70; public static final int READER_FILE_DOWNLOAD_PERMISSION_REQUEST_CODE = 80; + public static final int NOTIFICATIONS_PERMISSION_REQUEST_CODE = 90; + /** * called by the onRequestPermissionsResult() of various activities and fragments - tracks * the permission results, remembers that the permissions have been asked for, and optionally @@ -152,6 +156,8 @@ private static AppPrefs.PrefKey getPermissionAskedKey(@NonNull String permission return AppPrefs.UndeletablePrefKey.ASKED_PERMISSION_AUDIO_READ; case android.Manifest.permission.CAMERA: return AppPrefs.UndeletablePrefKey.ASKED_PERMISSION_CAMERA; + case Manifest.permission.POST_NOTIFICATIONS: + return AppPrefs.UndeletablePrefKey.ASKED_PERMISSION_NOTIFICATIONS; default: AppLog.w(AppLog.T.UTILS, "No key for requested permission"); return null; @@ -239,4 +245,20 @@ public static void showAppSettings(@NonNull Context context) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } + + /* + * open the device's notification settings page for this app so the user can edit permissions + */ + public static void showNotificationsSettings(@NonNull Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.O) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else { + // We can't open notifications settings screen directly. Instead, open the app settings. + showAppSettings(context); + } + } } diff --git a/WordPress/src/main/res/layout/notifications_list_fragment.xml b/WordPress/src/main/res/layout/notifications_list_fragment.xml index e37e140fb948..33205b7b7c42 100644 --- a/WordPress/src/main/res/layout/notifications_list_fragment.xml +++ b/WordPress/src/main/res/layout/notifications_list_fragment.xml @@ -21,12 +21,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 5abf76904a30..dddcb178007b 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1497,6 +1497,15 @@ Follows Likes + + Fix + Dismiss notification permission warning. + Push notifications are turned off. + Push notifications are turned off + You\'ll need to open the app to see notifications. + Go to Settings → Notifications → App Settings, and turn %1$s on to be notified immediately. + Turn on notifications + Notification Settings On @@ -4066,6 +4075,10 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> Notify me Update Done + Turn on push notifications + To use blogging reminders, you\'ll need to turn on push notifications. + @string/notifications_permission_bottom_sheet_description_2 + @string/notifications_permission_bottom_sheet_button All set! Reminders removed! You\'ll get reminders to blog %1$s a week on %2$s at %3$s. diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml index 5dced5d17a6e..cc97e79f5e00 100644 --- a/WordPress/src/main/res/values/styles.xml +++ b/WordPress/src/main/res/values/styles.xml @@ -1571,6 +1571,33 @@ sans-serif + + + + + + + + + - + - + + - +