Skip to content

Commit

Permalink
feat(mobile): add schedule input reminder notification (#231)
Browse files Browse the repository at this point in the history
Resolves #25
  • Loading branch information
bkdev98 authored Aug 20, 2024
1 parent 8ffbf18 commit bf6ab78
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 8 deletions.
33 changes: 32 additions & 1 deletion apps/mobile/app/(app)/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import Constants from 'expo-constants'

import { Logo } from '@/components/common/logo'
import { MenuItem } from '@/components/common/menu-item'
import { toast } from '@/components/common/toast'
import { ProfileCard } from '@/components/setting/profile-card'
import { SelectDefaultCurrency } from '@/components/setting/select-default-currency'
import { Button } from '@/components/ui/button'
import { Switch } from '@/components/ui/switch'
import { Text } from '@/components/ui/text'
import { useScheduleNotification } from '@/hooks/use-schedule-notification'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useLocale } from '@/locales/provider'
import { useUserSettingsStore } from '@/stores/user-settings/store'
import { useAuth } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { LinearGradient } from 'expo-linear-gradient'
import * as Notifications from 'expo-notifications'
import { Link } from 'expo-router'
import {
BellIcon,
Expand All @@ -39,6 +43,9 @@ export default function SettingsScreen() {
const { i18n } = useLingui()
const { language } = useLocale()
const { colorScheme } = useColorScheme()
const { cancelAllScheduledNotifications } = useScheduleNotification()
const { setEnabledPushNotifications, enabledPushNotifications } =
useUserSettingsStore()

return (
<View className="bg-card">
Expand Down Expand Up @@ -132,7 +139,30 @@ export default function SettingsScreen() {
label={t(i18n)`Push notifications`}
icon={BellIcon}
rightSection={
<Switch checked={false} onCheckedChange={console.log} />
<Switch
checked={enabledPushNotifications}
onCheckedChange={async (checked) => {
if (checked) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } =
await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
toast.error(t(i18n)`Push notifications are not enabled`)
setEnabledPushNotifications(false)
return
}
toast.success(t(i18n)`Push notifications are enabled`)
} else {
toast.success(t(i18n)`Push notifications are disabled`)
}
setEnabledPushNotifications(checked)
}}
/>
}
/>
</View>
Expand Down Expand Up @@ -188,6 +218,7 @@ export default function SettingsScreen() {
style: 'destructive',
onPress: async () => {
await signOut()
await cancelAllScheduledNotifications()
},
},
])
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BackButton } from '@/components/common/back-button'
import { Button } from '@/components/ui/button'
import { useScheduleNotificationTrigger } from '@/hooks/use-schedule-notification'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useUser } from '@clerk/clerk-expo'
Expand All @@ -13,6 +14,7 @@ export default function AuthenticatedLayout() {
const { user, isLoaded, isSignedIn } = useUser()
const { colorScheme } = useColorScheme()
const { i18n } = useLingui()
useScheduleNotificationTrigger()

useEffect(() => {
if (isLoaded) {
Expand Down
11 changes: 11 additions & 0 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import {
SpaceMono_400Regular,
SpaceMono_700Bold,
} from '@expo-google-fonts/space-mono'
import * as Notifications from 'expo-notifications'
import { SplashScreen, Stack, useNavigationContainerRef } from 'expo-router'
import * as WebBrowser from 'expo-web-browser'

import 'react-native-reanimated'
import { ToastRoot } from '@/components/common/toast'
import { useNotificationObserver } from '@/hooks/use-schedule-notification'
import { useWarmUpBrowser } from '@/hooks/use-warm-up-browser'
import { useColorScheme } from '@/hooks/useColorScheme'
import { queryClient } from '@/lib/client'
Expand Down Expand Up @@ -101,6 +103,14 @@ SplashScreen.preventAutoHideAsync()

WebBrowser.maybeCompleteAuthSession()

Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
})

// biome-ignore lint/style/useNamingConvention: <explanation>
export const unstable_settings = {
initialRouteName: '(app)',
Expand All @@ -119,6 +129,7 @@ function RootLayout() {
Inter_600SemiBold,
})
const ref = useNavigationContainerRef()
useNotificationObserver()

useEffect(() => {
const subscription = AppState.addEventListener('change', onAppStateChange)
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/app/onboarding/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default function OnboardingLayout() {
},
}}
/>
<Stack.Screen name="step-three" />
</Stack>
)
}
48 changes: 48 additions & 0 deletions apps/mobile/app/onboarding/step-three.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NotificationIllustration } from '@/components/svg-assets/notification-illustration'
import { Button } from '@/components/ui/button'
import { Text } from '@/components/ui/text'
import { useUserSettingsStore } from '@/stores/user-settings/store'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import * as Notifications from 'expo-notifications'
import { useRouter } from 'expo-router'
import { ScrollView, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

export default function StepThreeScreen() {
const { i18n } = useLingui()
const { bottom } = useSafeAreaInsets()
const router = useRouter()
const { setEnabledPushNotifications } = useUserSettingsStore()

async function handleEnableNotification() {
const { status } = await Notifications.requestPermissionsAsync()
if (status === 'granted') {
setEnabledPushNotifications(true)
}
router.replace('/')
}

return (
<ScrollView
className="bg-card"
contentContainerClassName="gap-4 p-8 pt-4 flex-1 justify-between"
automaticallyAdjustKeyboardInsets
keyboardShouldPersistTaps="handled"
contentContainerStyle={{ paddingBottom: bottom + 32 }}
>
<View className="gap-4">
<Text className="font-sans font-semibold text-3xl text-primary">
{t(i18n)`Enable spending alerts`}
</Text>
<Text className="font-sans text-muted-foreground">
{t(i18n)`Keeping up with your spending and budgets.`}
</Text>
</View>
<NotificationIllustration className="my-16 h-[326px] text-primary" />
<Button className="mx-auto" onPress={handleEnableNotification}>
<Text>{t(i18n)`Enable Push Notifications`}</Text>
</Button>
</ScrollView>
)
}
16 changes: 13 additions & 3 deletions apps/mobile/app/onboarding/step-two.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { createId } from '@paralleldrive/cuid2'
import * as Haptics from 'expo-haptics'
import * as Notifications from 'expo-notifications'
import { useRouter } from 'expo-router'
import {
Controller,
Expand Down Expand Up @@ -51,6 +52,9 @@ export function FormSubmitButton({
)
}

const budgetId = createId()
const periodId = createId()

export default function StepTwoScreen() {
const { i18n } = useLingui()
const defaultCurrency = useDefaultCurrency()
Expand All @@ -74,12 +78,12 @@ export default function StepTwoScreen() {
preferredCurrency: data.currency,
type: BudgetTypeSchema.Enum.SPENDING,
period: {
id: createId(),
id: periodId,
amount: data.amount,
type: BudgetPeriodTypeSchema.Enum.MONTHLY,
},
},
id: createId(),
id: budgetId,
}).catch(() => {
// ignore
})
Expand All @@ -90,7 +94,13 @@ export default function StepTwoScreen() {
},
})

router.replace('/')
const { status: existingStatus } = await Notifications.getPermissionsAsync()

if (existingStatus === 'granted') {
router.replace('/')
} else {
router.push('/onboarding/step-three')
}
}

return (
Expand Down
Loading

0 comments on commit bf6ab78

Please sign in to comment.