diff --git a/native/src/components/SettingItem.tsx b/native/src/components/SettingItem.tsx
index f73dac6eba..d69a10cfc5 100644
--- a/native/src/components/SettingItem.tsx
+++ b/native/src/components/SettingItem.tsx
@@ -35,6 +35,11 @@ const FlexEndContainer = styled.View`
padding: 0 5px;
`
+const BadgeContainer = styled.View`
+ flex-direction: row;
+ align-items: center;
+`
+
const Badge = styled.View<{ enabled: boolean }>`
width: 8px;
height: 8px;
@@ -42,50 +47,61 @@ const Badge = styled.View<{ enabled: boolean }>`
background-color: ${props => (props.enabled ? 'limegreen' : 'red')};
`
+type SettingItemValueProps = {
+ onPress: () => void
+ hasBadge: boolean
+ value: boolean
+}
+
+const SettingsItemValue = ({ value, hasBadge, onPress }: SettingItemValueProps): ReactElement => {
+ const { t } = useTranslation('settings')
+ if (hasBadge) {
+ return (
+
+
+ {value ? t('enabled') : t('disabled')}
+
+ )
+ }
+ return
+}
+
type SettingItemProps = {
title: string
description?: string
onPress: () => void
bigTitle?: boolean
role?: Role
- hasSwitch?: boolean
hasBadge?: boolean
- value: boolean
+ value: boolean | null
}
-const SettingItem = (props: SettingItemProps): ReactElement => {
- const { title, description, onPress, value, hasBadge, hasSwitch, bigTitle, role } = props
- const { t } = useTranslation('settings')
-
- return (
-
-
-
+const SettingItem = ({
+ title,
+ description,
+ onPress,
+ value,
+ bigTitle,
+ role,
+ hasBadge = false,
+}: SettingItemProps): ReactElement => (
+
+
+
+
+ {title}
+
+ {!!description && (
- {title}
+ {description}
- {!!description && (
-
- {description}
-
- )}
-
-
- {hasSwitch && }
- {hasBadge && (
-
-
- {value ? t('enabled') : t('disabled')}
-
- )}
-
-
-
- )
-}
+ )}
+
+
+ {value !== null && }
+
+
+
+)
export default SettingItem
diff --git a/native/src/routes/Settings.tsx b/native/src/routes/Settings.tsx
index 66e176e8fb..d88696f056 100644
--- a/native/src/routes/Settings.tsx
+++ b/native/src/routes/Settings.tsx
@@ -1,7 +1,6 @@
import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
-import { SectionList, SectionListData } from 'react-native'
-import styled from 'styled-components/native'
+import { FlatList } from 'react-native'
import { SettingsRouteType } from 'shared'
@@ -19,15 +18,6 @@ type SettingsProps = {
navigation: NavigationProps
}
-type SectionType = SectionListData & {
- title?: string | null
-}
-
-const SectionHeader = styled.Text`
- padding: 20px;
- color: ${props => props.theme.colors.textColor};
-`
-
const Settings = ({ navigation }: SettingsProps): ReactElement => {
const appContext = useCityAppContext()
const showSnackbar = useSnackbar()
@@ -46,36 +36,24 @@ const Settings = ({ navigation }: SettingsProps): ReactElement => {
const renderItem = ({ item }: { item: SettingsSectionType }) => {
const { getSettingValue, onPress, ...otherProps } = item
- const value = !!(getSettingValue && getSettingValue(settings))
+ const value = getSettingValue ? !!getSettingValue(settings) : null
return
}
- const renderSectionHeader = ({ section: { title } }: { section: SectionType }) => {
- if (!title) {
- return null
- }
-
- return {title}
- }
-
- const sections = createSettingsSections({
- appContext,
- navigation,
- showSnackbar,
- t,
- })
+ const sections = createSettingsSections({ appContext, navigation, showSnackbar, t }).filter(
+ (it): it is SettingsSectionType => it !== null,
+ )
return (
-
+
)
diff --git a/native/src/utils/__tests__/createSettingsSections.spec.ts b/native/src/utils/__tests__/createSettingsSections.spec.ts
index bd6eb9f7db..5cc737f42b 100644
--- a/native/src/utils/__tests__/createSettingsSections.spec.ts
+++ b/native/src/utils/__tests__/createSettingsSections.spec.ts
@@ -48,20 +48,20 @@ describe('createSettingsSections', () => {
navigation,
showSnackbar,
t,
- })[0]!.data
+ })
describe('allowPushNotifications', () => {
it('should not include push notification setting if disabled', () => {
mockedPushNotificationsEnabled.mockImplementation(() => false)
const sections = createSettings()
- expect(sections.find(it => it.title === 'privacyPolicy')).toBeTruthy()
- expect(sections.find(it => it.title === 'pushNewsTitle')).toBeFalsy()
+ expect(sections.find(it => it?.title === 'privacyPolicy')).toBeTruthy()
+ expect(sections.find(it => it?.title === 'pushNewsTitle')).toBeFalsy()
})
it('should set correct setting on press', async () => {
mockedPushNotificationsEnabled.mockImplementation(() => true)
const sections = createSettings()
- const pushNotificationSection = sections.find(it => it.title === 'pushNewsTitle')!
+ const pushNotificationSection = sections.find(it => it?.title === 'pushNewsTitle')!
await pushNotificationSection!.onPress()
expect(updateSettings).toHaveBeenCalledTimes(1)
expect(updateSettings).toHaveBeenCalledWith({ allowPushNotifications: false })
@@ -77,7 +77,7 @@ describe('createSettingsSections', () => {
it('should unsubscribe from push notification topic', async () => {
mockedPushNotificationsEnabled.mockImplementation(() => true)
const sections = createSettings()
- const pushNotificationSection = sections.find(it => it.title === 'pushNewsTitle')!
+ const pushNotificationSection = sections.find(it => it?.title === 'pushNewsTitle')!
expect(mockUnsubscribeNews).not.toHaveBeenCalled()
@@ -95,7 +95,7 @@ describe('createSettingsSections', () => {
it('should subscribe to push notification topic if permission is granted', async () => {
mockedPushNotificationsEnabled.mockImplementation(() => true)
const sections = createSettings({ allowPushNotifications: false })
- const pushNotificationSection = sections.find(it => it.title === 'pushNewsTitle')!
+ const pushNotificationSection = sections.find(it => it?.title === 'pushNewsTitle')!
expect(mockRequestPushNotificationPermission).not.toHaveBeenCalled()
expect(mockSubscribeNews).not.toHaveBeenCalled()
@@ -120,7 +120,7 @@ describe('createSettingsSections', () => {
it('should open settings and return false if permissions not granted', async () => {
mockedPushNotificationsEnabled.mockImplementation(() => true)
const sections = createSettings({ allowPushNotifications: false })
- const pushNotificationSection = sections.find(it => it.title === 'pushNewsTitle')!
+ const pushNotificationSection = sections.find(it => it?.title === 'pushNewsTitle')!
expect(mockRequestPushNotificationPermission).not.toHaveBeenCalled()
expect(mockSubscribeNews).not.toHaveBeenCalled()
diff --git a/native/src/utils/createSettingsSections.ts b/native/src/utils/createSettingsSections.ts
index e10a547358..8c06a7e647 100644
--- a/native/src/utils/createSettingsSections.ts
+++ b/native/src/utils/createSettingsSections.ts
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/react-native'
import { TFunction } from 'i18next'
-import { Role, SectionListData } from 'react-native'
+import { Role } from 'react-native'
import { openSettings } from 'react-native-permissions'
import { CONSENT_ROUTE, JPAL_TRACKING_ROUTE, LICENSES_ROUTE, SettingsRouteType } from 'shared'
@@ -26,7 +26,6 @@ export type SettingsSectionType = {
onPress: () => Promise | void
bigTitle?: boolean
role?: Role
- hasSwitch?: boolean
hasBadge?: boolean
getSettingValue?: (settings: SettingsType) => boolean | null
}
@@ -49,123 +48,105 @@ const createSettingsSections = ({
navigation,
showSnackbar,
t,
-}: CreateSettingsSectionsProps): Readonly>> => [
- {
- title: null,
- data: [
- ...(!pushNotificationsEnabled()
- ? []
- : [
- {
- title: t('pushNewsTitle'),
- description: t('pushNewsDescription'),
- hasSwitch: true,
- getSettingValue: (settings: SettingsType) => settings.allowPushNotifications,
- onPress: async () => {
- const allowPushNotifications = !settings.allowPushNotifications
- updateSettings({ allowPushNotifications })
- if (!allowPushNotifications) {
- await unsubscribeNews(cityCode, languageCode)
- return
- }
+}: CreateSettingsSectionsProps): (SettingsSectionType | null)[] => [
+ pushNotificationsEnabled()
+ ? {
+ title: t('pushNewsTitle'),
+ description: t('pushNewsDescription'),
+ getSettingValue: (settings: SettingsType) => settings.allowPushNotifications,
+ onPress: async () => {
+ const allowPushNotifications = !settings.allowPushNotifications
+ updateSettings({ allowPushNotifications })
+ if (!allowPushNotifications) {
+ await unsubscribeNews(cityCode, languageCode)
+ return
+ }
- const status = await requestPushNotificationPermission(updateSettings)
+ const status = await requestPushNotificationPermission(updateSettings)
- if (status) {
- await subscribeNews({ cityCode, languageCode, allowPushNotifications, skipSettingsCheck: true })
- } else {
- updateSettings({ allowPushNotifications: false })
- // If the user has rejected the permission once, it can only be changed in the system settings
- showSnackbar({
- text: 'noPushNotificationPermission',
- positiveAction: {
- label: t('layout:settings'),
- onPress: openSettings,
- },
- })
- }
+ if (status) {
+ await subscribeNews({ cityCode, languageCode, allowPushNotifications, skipSettingsCheck: true })
+ } else {
+ updateSettings({ allowPushNotifications: false })
+ // If the user has rejected the permission once, it can only be changed in the system settings
+ showSnackbar({
+ text: 'noPushNotificationPermission',
+ positiveAction: {
+ label: t('layout:settings'),
+ onPress: openSettings,
},
- },
- ]),
- {
- title: t('sentryTitle'),
- description: t('sentryDescription', {
- appName: buildConfig().appName,
- }),
- hasSwitch: true,
- getSettingValue: (settings: SettingsType) => settings.errorTracking,
- onPress: async () => {
- const errorTracking = !settings.errorTracking
- updateSettings({ errorTracking })
-
- const client = Sentry.getClient()
- if (errorTracking && !client) {
- initSentry()
- } else if (client) {
- client.getOptions().enabled = errorTracking
+ })
}
},
- },
- {
- title: t('externalResourcesTitle'),
- description: t('externalResourcesDescription'),
- onPress: () => {
- navigation.navigate(CONSENT_ROUTE)
- },
- },
- {
- role: 'link',
- title: t('about', {
- appName: buildConfig().appName,
- }),
- onPress: async () => {
- const { aboutUrls } = buildConfig()
- const aboutUrl = aboutUrls[languageCode] || aboutUrls.default
- await openExternalUrl(aboutUrl, showSnackbar)
- },
- },
- {
- role: 'link',
- title: t('privacyPolicy'),
- onPress: async () => {
- const { privacyUrls } = buildConfig()
- const privacyUrl = privacyUrls[languageCode] || privacyUrls.default
- await openExternalUrl(privacyUrl, showSnackbar)
- },
- },
- {
- title: t('version', {
- version: NativeConstants.appVersion,
- }),
- onPress: () => {
- volatileValues.versionTaps += 1
+ }
+ : null,
+ {
+ title: t('sentryTitle'),
+ description: t('sentryDescription', { appName: buildConfig().appName }),
+ getSettingValue: (settings: SettingsType) => settings.errorTracking,
+ onPress: async () => {
+ const errorTracking = !settings.errorTracking
+ updateSettings({ errorTracking })
- if (volatileValues.versionTaps === TRIGGER_VERSION_TAPS) {
- volatileValues.versionTaps = 0
- throw Error('This error was thrown for testing purposes. Please ignore this error.')
- }
- },
- },
- {
- title: t('openSourceLicenses'),
- onPress: () => navigation.navigate(LICENSES_ROUTE),
- },
- // Only show the jpal tracking setting for users that opened it via deep link before
- ...(buildConfig().featureFlags.jpalTracking && settings.jpalTrackingCode
- ? [
- {
- title: t('tracking'),
- description: t('trackingShortDescription', { appName: buildConfig().appName }),
- getSettingValue: (settings: SettingsType) => settings.jpalTrackingEnabled,
- hasBadge: true,
- onPress: () => {
- navigation.navigate(JPAL_TRACKING_ROUTE)
- },
- },
- ]
- : []),
- ],
+ const client = Sentry.getClient()
+ if (errorTracking && !client) {
+ initSentry()
+ } else if (client) {
+ client.getOptions().enabled = errorTracking
+ }
+ },
+ },
+ {
+ title: t('externalResourcesTitle'),
+ description: t('externalResourcesDescription'),
+ onPress: () => navigation.navigate(CONSENT_ROUTE),
+ },
+ {
+ role: 'link',
+ title: t('about', {
+ appName: buildConfig().appName,
+ }),
+ onPress: async () => {
+ const { aboutUrls } = buildConfig()
+ const aboutUrl = aboutUrls[languageCode] || aboutUrls.default
+ await openExternalUrl(aboutUrl, showSnackbar)
+ },
+ },
+ {
+ role: 'link',
+ title: t('privacyPolicy'),
+ onPress: async () => {
+ const { privacyUrls } = buildConfig()
+ const privacyUrl = privacyUrls[languageCode] || privacyUrls.default
+ await openExternalUrl(privacyUrl, showSnackbar)
+ },
},
+ {
+ title: t('version', { version: NativeConstants.appVersion }),
+ onPress: () => {
+ volatileValues.versionTaps += 1
+
+ if (volatileValues.versionTaps === TRIGGER_VERSION_TAPS) {
+ volatileValues.versionTaps = 0
+ throw Error('This error was thrown for testing purposes. Please ignore this error.')
+ }
+ },
+ },
+ {
+ title: t('openSourceLicenses'),
+ onPress: () => navigation.navigate(LICENSES_ROUTE),
+ },
+ buildConfig().featureFlags.jpalTracking && settings.jpalTrackingCode
+ ? {
+ title: t('tracking'),
+ description: t('trackingShortDescription', { appName: buildConfig().appName }),
+ getSettingValue: (settings: SettingsType) => settings.jpalTrackingEnabled,
+ hasBadge: true,
+ onPress: () => {
+ navigation.navigate(JPAL_TRACKING_ROUTE)
+ },
+ }
+ : null,
]
export default createSettingsSections