Skip to content

Commit

Permalink
Notification settings improvements (#3264)
Browse files Browse the repository at this point in the history
  • Loading branch information
michellebrier authored May 3, 2023
1 parent 59710d6 commit 492fa67
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 35 deletions.
24 changes: 24 additions & 0 deletions packages/common/src/store/pages/settings/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const TOGGLE_PUSH_NOTIFICATION_SETTING =
export const TOGGLE_PUSH_NOTIFICATION_SETTING_FAILED =
'SETTINGS_PAGE/TOGGLE_PUSH_NOTIFICATION_SETTING_FAILED'

export const REQUEST_PUSH_NOTIFICATION_PERMISSIONS =
'SETTINGS_PAGE/REQUEST_PUSH_NOTIFICATION_PERMISSIONS'

export const UPDATE_EMAIL_FREQUENCY = 'SETTINGS_PAGE/UPDATE_EMAIL_FREQUENCY'

export const GET_NOTIFICATION_SETTINGS =
Expand All @@ -36,6 +39,8 @@ export const SET_BROWSER_NOTIFICATION_ENABLED =
'SETTINGS/SET_BROWSER_NOTIFICATION_ENABLED'
export const SET_BROWSER_NOTIFICATION_SETTINGS_ON =
'SETTINGS/SET_BROWSER_NOTIFICATION_SETTINGS_ON'
export const SET_BROWSER_NOTIFICATION_SETTINGS_OFF =
'SETTINGS/SET_BROWSER_NOTIFICATION_SETTINGS_OFF'

export const BROWSER_PUSH_NOTIFICATION_FAILED =
'SETTINGS/BROWSER_PUSH_NOTIFICATION_FAILED'
Expand Down Expand Up @@ -91,6 +96,12 @@ export function togglePushNotificationSettingFailed(
}
}

export function requestPushNotificationPermissions() {
return {
type: REQUEST_PUSH_NOTIFICATION_PERMISSIONS
}
}

export function updateEmailFrequency(
frequency: EmailFrequency,
updateServer = true
Expand All @@ -113,6 +124,10 @@ export function setBrowserNotificationSettingsOn() {
return { type: SET_BROWSER_NOTIFICATION_SETTINGS_ON }
}

export function setBrowserNotificationSettingsOff() {
return { type: SET_BROWSER_NOTIFICATION_SETTINGS_OFF }
}

export function browserPushNotificationFailed(error: string) {
return { type: BROWSER_PUSH_NOTIFICATION_FAILED, error }
}
Expand All @@ -131,6 +146,10 @@ export type TogglePushNotificationSettingFailed = ReturnType<
typeof togglePushNotificationSettingFailed
>

export type RequestPushNotificationPermissions = ReturnType<
typeof requestPushNotificationPermissions
>

export type UpdateEmailFrequency = ReturnType<typeof updateEmailFrequency>

export type GetNotificationSettings = ReturnType<typeof getNotificationSettings>
Expand All @@ -157,13 +176,17 @@ export type SetBrowserNotificationEnabled = ReturnType<
export type SetBrowserNotificationSettingsOn = ReturnType<
typeof setBrowserNotificationSettingsOn
>
export type SetBrowserNotificationSettingsOff = ReturnType<
typeof setBrowserNotificationSettingsOff
>
export type BrowserPushNotificationFailed = ReturnType<
typeof browserPushNotificationFailed
>

export type SetAiAttribution = ReturnType<typeof setAiAttribution>

export type SettingActions =
| RequestPushNotificationPermissions
| ToggleNotificationSetting
| TogglePushNotificationSetting
| TogglePushNotificationSettingFailed
Expand All @@ -177,4 +200,5 @@ export type SettingActions =
| SetBrowserNotificationPermission
| SetBrowserNotificationEnabled
| SetBrowserNotificationSettingsOn
| SetBrowserNotificationSettingsOff
| BrowserPushNotificationFailed
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import IconCoSign from 'app/assets/images/iconCoSign.svg'
import IconFollow from 'app/assets/images/iconFollow.svg'
import IconNotification from 'app/assets/images/iconGradientNotification.svg'
import IconHeart from 'app/assets/images/iconHeart.svg'
import IconMessage from 'app/assets/images/iconMessage.svg'
import IconNewReleases from 'app/assets/images/iconNewReleases.svg'
import IconRemix from 'app/assets/images/iconRemix.svg'
import IconRepost from 'app/assets/images/iconRepost.svg'
Expand All @@ -28,6 +29,7 @@ const messages = {
coSigns: 'Co-Signs',
remixes: 'Remixes',
newReleases: 'New Releases',
messages: 'Messages',
enable: 'Enable Notifications'
}

Expand Down Expand Up @@ -55,6 +57,10 @@ const actions = [
{
label: messages.newReleases,
icon: IconNewReleases
},
{
label: messages.messages,
icon: IconMessage
}
]

Expand Down
11 changes: 4 additions & 7 deletions packages/mobile/src/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import type { PushNotificationPermissions } from 'react-native'
import { Platform } from 'react-native'
import { Notifications } from 'react-native-notifications'
import type { Registered, Notification } from 'react-native-notifications'
Expand Down Expand Up @@ -89,17 +88,15 @@ class PushNotifications {
}
}

async hasPermission(): Promise<boolean> {
return await Notifications.isRegisteredForRemoteNotifications()
}

requestPermission() {
isRegistering = true
Notifications.registerRemoteNotifications()
}

async checkPermission(
callback: (permissions: PushNotificationPermissions) => void
) {
Notifications.ios.checkPermissions().then(callback)
}

cancelNotif() {
Notifications.cancelLocalNotification(this.lastId)
}
Expand Down
97 changes: 78 additions & 19 deletions packages/mobile/src/store/settings/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,61 @@ import {
} from '@audius/common'
import { waitForRead } from 'audius-client/src/utils/sagaHelpers'
import commonSettingsSagas from 'common/store/pages/settings/sagas'
import { mapValues } from 'lodash'
import { select, call, put, takeEvery } from 'typed-redux-saga'

import PushNotifications from 'app/notifications'

const { getPushNotificationSettings } = settingsPageSelectors
const { getAccountUser } = accountSelectors

export function* disablePushNotifications() {
export function* deregisterPushNotifications() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
const { token } = yield* call([PushNotifications, 'getToken'])
PushNotifications.deregister()
yield* call(audiusBackendInstance.deregisterDeviceToken, token)
}

function* enablePushNotifications() {
yield* call([PushNotifications, 'requestPermission'])
const { token, os } = yield* call([PushNotifications, 'getToken'])

const audiusBackendInstance = yield* getContext('audiusBackendInstance')

// Enabling push notifications should enable all of the notification types
const newSettings = { ...initialState.pushNotifications }
yield* put(actions.setPushNotificationSettings(newSettings))

// We need a user for this to work (and in the case of sign up, we might not
// have one right away when this function is called)
yield* call(waitForValue, getAccountUser)
yield* call(audiusBackendInstance.updatePushNotificationSettings, newSettings)

yield* call(audiusBackendInstance.registerDeviceToken, token, os)
}

function* disablePushNotifications() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
const newSettings = mapValues(
initialState.pushNotifications,
function (_val: boolean) {
return false
}
)
yield* put(actions.setPushNotificationSettings(newSettings))
yield* call(waitForValue, getAccountUser)
yield* call(audiusBackendInstance.updatePushNotificationSettings, newSettings)
yield* call(deregisterPushNotifications)
}

function pushNotificationsEnabled(settings: TPushNotifications): boolean {
for (const key in initialState.pushNotifications) {
if (key === PushNotificationSetting.MobilePush) continue
if (settings[key]) return true
}
return false
}

function* watchGetPushNotificationSettings() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
yield* takeEvery(actions.GET_PUSH_NOTIFICATION_SETTINGS, function* () {
Expand All @@ -33,9 +74,21 @@ function* watchGetPushNotificationSettings() {
const settings = (yield* call(
audiusBackendInstance.getPushNotificationSettings
)) as TPushNotifications
const pushNotificationSettings = {
...settings,
[PushNotificationSetting.MobilePush]: !!settings
let pushNotificationSettings = mapValues(
initialState.pushNotifications,
function (_val: boolean) {
return false
}
)

if (settings) {
pushNotificationSettings = {
...settings,
[PushNotificationSetting.MobilePush]: yield* call(
pushNotificationsEnabled,
settings
)
}
}
yield* put(actions.setPushNotificationSettings(pushNotificationSettings))
} catch (error) {
Expand All @@ -56,20 +109,7 @@ function* watchUpdatePushNotificationSettings() {
try {
if (action.notificationType === PushNotificationSetting.MobilePush) {
if (isOn) {
yield* call([PushNotifications, 'requestPermission'])
const { token, os } = yield* call([PushNotifications, 'getToken'])
// Enabling push notifications should enable all of the notification types
const newSettings = { ...initialState.pushNotifications }
yield* put(actions.setPushNotificationSettings(newSettings))

// We need a user for this to work (and in the case of sign up, we might not
// have one right away when this function is called)
yield* call(waitForValue, getAccountUser)
yield* call(
audiusBackendInstance.updatePushNotificationSettings,
newSettings
)
yield* call(audiusBackendInstance.registerDeviceToken, token, os)
yield* call(enablePushNotifications)
} else {
yield* call(disablePushNotifications)
}
Expand All @@ -96,10 +136,29 @@ function* watchUpdatePushNotificationSettings() {
)
}

function* watchRequestPushNotificationPermissions() {
yield* takeEvery(
actions.REQUEST_PUSH_NOTIFICATION_PERMISSIONS,
function* (_action: actions.RequestPushNotificationPermissions) {
const hasPermissions = yield* call([PushNotifications, 'hasPermission'])
if (!hasPermissions) {
// Request permission to send push notifications and enable all if accepted
yield* put(
actions.togglePushNotificationSetting(
PushNotificationSetting.MobilePush,
true
)
)
}
}
)
}

export default function sagas() {
return [
...commonSettingsSagas(),
watchGetPushNotificationSettings,
watchUpdatePushNotificationSettings
watchUpdatePushNotificationSettings,
watchRequestPushNotificationPermissions
]
}
4 changes: 2 additions & 2 deletions packages/mobile/src/store/sign-out/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { localStorage } from 'app/services/local-storage'
import { resetOAuthState } from '../oauth/actions'
import { clearOfflineDownloads } from '../offline-downloads/slice'
import { clearHistory } from '../search/searchSlice'
import { disablePushNotifications } from '../settings/sagas'
import { deregisterPushNotifications } from '../settings/sagas'

const { resetAccount } = accountActions
const { resetState: resetWalletState } = tokenDashboardPageActions
Expand All @@ -47,7 +47,7 @@ function* signOut() {
yield* put(clearOfflineDownloads())
yield* put(resetWalletState())

yield* call(disablePushNotifications)
yield* call(deregisterPushNotifications)
yield* call([localStorage, 'clearAudiusAccount'])
yield* call([localStorage, 'clearAudiusAccountUser'])
yield* call([audiusBackendInstance, 'signOut'])
Expand Down
14 changes: 7 additions & 7 deletions packages/web/src/common/store/pages/signon/sagas.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ import { watchSignOnError } from './errorSagas'
import { getRouteOnCompletion, getSignOn } from './selectors'
import { FollowArtistsCategory, Pages } from './types'
import { checkHandle } from './verifiedChecker'
const { togglePushNotificationSetting } = settingsPageActions

const { requestPushNotificationPermissions, togglePushNotificationSetting } =
settingsPageActions
const { getFeePayer } = solanaSelectors
const { saveCollection } = collectionsSocialActions
const { getUsers } = cacheUsersSelectors
Expand Down Expand Up @@ -426,6 +428,7 @@ function* signUp() {

const isNativeMobile = yield getContext('isNativeMobile')
if (isNativeMobile) {
// Request permission to send push notifications and enable all if accepted
yield put(
togglePushNotificationSetting(
PushNotificationSetting.MobilePush,
Expand Down Expand Up @@ -531,12 +534,9 @@ function* signIn(action) {
yield put(signOnActions.resetSignOn())
const isNativeMobile = yield getContext('isNativeMobile')
if (isNativeMobile) {
yield put(
togglePushNotificationSetting(
PushNotificationSetting.MobilePush,
true
)
)
// If permissions not already enabled, request permission to send push notifications
// and enable all if accepted
yield put(requestPushNotificationPermissions())
} else {
setHasRequestedBrowserPermission()
yield put(accountActions.showPushNotificationConfirmation())
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/pages/settings-page/SettingsPageProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class SettingsPage extends PureComponent<
if (!isOn) {
// if turning off, set all settings values to false
this.props.setBrowserNotificationEnabled(false)
this.props.setBrowserNotificationSettingsOff()
} else if (
this.props.notificationSettings.permission === Permission.GRANTED
) {
Expand Down Expand Up @@ -264,6 +265,8 @@ function mapDispatchToProps(dispatch: Dispatch) {
dispatch(accountActions.subscribeBrowserPushNotifications()),
setBrowserNotificationSettingsOn: () =>
dispatch(settingPageActions.setBrowserNotificationSettingsOn()),
setBrowserNotificationSettingsOff: () =>
dispatch(settingPageActions.setBrowserNotificationSettingsOff()),
setNotificationSettings: (settings: object) =>
dispatch(settingPageActions.setNotificationSettings(settings)),
setBrowserNotificationEnabled: (enabled: boolean) =>
Expand Down
29 changes: 29 additions & 0 deletions packages/web/src/pages/settings-page/store/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,34 @@ function* watchSetBrowserNotificationSettingsOn() {
)
}

function* watchSetBrowserNotificationSettingsOff() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
yield* takeEvery(
actions.SET_BROWSER_NOTIFICATION_SETTINGS_OFF,
function* (action: actions.SetBrowserNotificationSettingsOff) {
try {
const updatedSettings = {
[BrowserNotificationSetting.MilestonesAndAchievements]: false,
[BrowserNotificationSetting.Followers]: false,
[BrowserNotificationSetting.Reposts]: false,
[BrowserNotificationSetting.Favorites]: false,
[BrowserNotificationSetting.Remixes]: false,
[BrowserNotificationSetting.Messages]: false
}
yield* put(actions.setNotificationSettings(updatedSettings))
yield* call(
audiusBackendInstance.updateNotificationSettings,
updatedSettings
)
} catch (error) {
yield* put(
actions.browserPushNotificationFailed(getErrorMessage(error))
)
}
}
)
}

function* watchUpdateNotificationSettings() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
yield* takeEvery(
Expand Down Expand Up @@ -201,6 +229,7 @@ export default function sagas() {
...commonSettingsSagas(),
watchGetSettings,
watchSetBrowserNotificationSettingsOn,
watchSetBrowserNotificationSettingsOff,
watchToogleBrowserPushNotification,
watchUpdateNotificationSettings
]
Expand Down

0 comments on commit 492fa67

Please sign in to comment.