Mobile app (#3)

* Initial Capacitor Config and plugins

* Add Android project files and resources

* Add local notification scheduling for chores

* Add NotificationAccessSnackbar component for handling notification preferences

* Add capacitor-preferences to Android project

* Update notification Snackbar

* Add local notification scheduling for chores

* Add ionic.config.json file for custom project configuration

* chore: Add environment variables for production deployment

* Add Support for IOS, pass notificaiton token(push notifications)

* Add Capacitor Device support and refactor notification handling

* Refactor GoogleAuth client IDs to use environment variables

* Remove google-services.json to enhance security by eliminating sensitive data from the repository

* Remove environment files to enhance security by eliminating sensitive data from the repository

* Rename project from fe-template to Donetick in ionic.config.json

* Remove GoogleService-Info.plist and Info.plist to enhance security by eliminating sensitive data from the repository

---------

Co-authored-by: Mo Tarbin <mohamad@Mos-MacBook-Pro.local>
This commit is contained in:
Mohamad Tarbin 2024-12-26 02:13:47 -05:00 committed by GitHub
parent 1e7b47e783
commit bcd32a8616
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 6699 additions and 880 deletions

View file

@ -1,10 +1,74 @@
import { Button, Divider, Input, Option, Select, Typography } from '@mui/joy'
import { useContext, useState } from 'react'
import { Box, Button, Card, Checkbox, Divider, FormControl, FormHelperText, FormLabel, IconButton, Input, List, ListItem, Option, Select, Snackbar, Switch, Typography } from '@mui/joy'
import React, { useContext, useEffect, useState } from 'react'
import { UserContext } from '../../contexts/UserContext'
import { GetUserProfile, UpdateUserDetails } from '../../utils/Fetcher'
import { Capacitor } from '@capacitor/core'
import { Preferences } from '@capacitor/preferences'
import { LocalNotifications } from '@capacitor/local-notifications'
import { Close } from '@mui/icons-material'
import { PushNotifications } from '@capacitor/push-notifications'
import { UpdateNotificationTarget } from '../../utils/Fetcher'
const NotificationSetting = () => {
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
const { userProfile, setUserProfile } = useContext(UserContext)
useEffect(() => {
if (!userProfile) {
GetUserProfile().then(resp => {
resp.json().then(data => {
setUserProfile(data.res)
setChatID(data.res.chatID)
})
})
}
}, [])
const getNotificationPreferences = async () => {
const ret = await Preferences.get({ key: 'notificationPreferences' });
return JSON.parse(ret.value);
};
const setNotificationPreferences = async (value) => {
if (value.granted === false){
await Preferences.set({ key: 'notificationPreferences', value: JSON.stringify({ granted: false }) });
return
}
const currentSettings = await getNotificationPreferences();
await Preferences.set({ key: 'notificationPreferences', value: JSON.stringify({ ...currentSettings, ...value }) });
};
const getPushNotificationPreferences = async () => {
const ret = await Preferences.get({ key: 'pushNotificationPreferences' });
return JSON.parse(ret.value);
};
const setPushNotificationPreferences = async (value) => {
await Preferences.set({ key: 'pushNotificationPreferences', value: JSON.stringify(value) });
};
const [deviceNotification, setDeviceNotification] = useState(
false
)
const [dueNotification, setDueNotification] = useState(true)
const [preDueNotification, setPreDueNotification] = useState(false)
const [naggingNotification, setNaggingNotification] = useState(false)
const [pushNotification, setPushNotification] = useState(
false
)
useEffect(() => {
getNotificationPreferences().then((resp) => {
setDeviceNotification(resp.granted)
setDueNotification(resp.dueNotification)
setPreDueNotification(resp.preDueNotification)
setNaggingNotification(resp.naggingNotification)
}
)
getPushNotificationPreferences().then((resp) => {
setPushNotification(resp.granted)
}
)
}, [])
const [notificationTarget, setNotificationTarget] = useState(
userProfile?.notification_target
? String(userProfile.notification_target.type)
@ -57,10 +121,236 @@ const NotificationSetting = () => {
}
return (
<div className='grid gap-4 py-4' id='notifications'>
<Typography level='h3'>Notification Settings</Typography>
<Typography level='h3'>Device Notification</Typography>
<Divider />
<Typography level='body-md'>Manage your notification settings</Typography>
<Typography level='body-md'>Manage your Device Notificaiton</Typography>
<FormControl
orientation="horizontal"
sx={{ width: 400, justifyContent: 'space-between' }}
>
<div>
<FormLabel>Device Notification</FormLabel>
<FormHelperText sx={{ mt: 0 }}>{Capacitor.isNativePlatform()? 'Receive notification on your device when a task is due' : 'This feature is only available on mobile devices'} </FormHelperText>
</div>
<Switch
disabled={!Capacitor.isNativePlatform()}
checked={deviceNotification}
onClick={(event) =>{
event.preventDefault()
if (deviceNotification === false){
LocalNotifications.requestPermissions().then((resp) => {
if (resp.display === 'granted') {
setDeviceNotification(true)
setNotificationPreferences({granted: true})
}
else if (resp.display === 'denied') {
setIsSnackbarOpen(true)
setDeviceNotification(false)
setNotificationPreferences({granted: false})
}
})
}
else{
setDeviceNotification(false)
}
}
}
color={deviceNotification ? 'success' : 'neutral'}
variant={deviceNotification ? 'solid' : 'outlined'}
endDecorator={deviceNotification ? 'On' : 'Off'}
slotProps={{
endDecorator: {
sx: {
minWidth: 24,
},
},
}}
/>
</FormControl>
{deviceNotification && (
<Card>
{[
{
'title': 'Due Date Notification',
'checked': dueNotification,
'set': setDueNotification,
'label': 'Notification when the task is due',
'property': 'dueNotification',
'disabled': false
},
{
'title': 'Pre-Due Date Notification',
'checked': preDueNotification,
'set': setPreDueNotification,
'label': 'Notification a few hours before the task is due',
'property': 'preDueNotification',
'disabled': true
},
{
'title': 'Overdue Notification',
'checked': naggingNotification,
'set': setNaggingNotification,
'label': 'Notification when the task is overdue',
'property': 'naggingNotification',
'disabled': true
}
]
.map(item => (
<FormControl
orientation="horizontal"
sx={{ width: 385, justifyContent: 'space-between' }}
>
<div>
<FormLabel>{item.title}</FormLabel>
<FormHelperText sx={{ mt: 0 }}>{item.label} </FormHelperText>
</div>
<Switch checked={item.checked}
disabled={item.disabled}
onClick={() =>{
setNotificationPreferences({[item.property]: !item.checked})
item.set(!item.checked)
}}
color={item.checked ? 'success' : ''}
variant='solid' endDecorator={item.checked ? 'On' : 'Off'} slotProps={{ endDecorator: { sx: { minWidth: 24 } } }} />
</FormControl>
))}
</Card>
)}
{/* <FormControl
orientation="horizontal"
sx={{ width: 400, justifyContent: 'space-between' }}
>
<div>
<FormLabel>Push Notifications</FormLabel>
<FormHelperText sx={{ mt: 0 }}>{Capacitor.isNativePlatform()? 'Receive push notification when someone complete task' : 'This feature is only available on mobile devices'} </FormHelperText>
</div>
<Switch
disabled={!Capacitor.isNativePlatform()}
checked={pushNotification}
onClick={(event) =>{
event.preventDefault()
if (pushNotification === false){
PushNotifications.requestPermissions().then((resp) => {
console.log("user PushNotifications permission",resp);
if (resp.receive === 'granted') {
setPushNotification(true)
setPushNotificationPreferences({granted: true})
}
if (resp.receive!== 'granted') {
setIsSnackbarOpen(true)
setPushNotification(false)
setPushNotificationPreferences({granted: false})
console.log("User denied permission", resp)
}
})
}
else{
setPushNotification(false)
}
}
}
color={pushNotification ? 'success' : 'neutral'}
variant={pushNotification ? 'solid' : 'outlined'}
endDecorator={pushNotification ? 'On' : 'Off'}
slotProps={{
endDecorator: {
sx: {
minWidth: 24,
},
},
}}
/>
</FormControl> */}
<Button
variant='soft'
color='primary'
sx={{
width: '210px',
mb: 1,
}}
onClick={() => {
// schedule a local notification in 5 seconds
LocalNotifications.schedule({
notifications: [
{
title: 'Task Reminder',
body: 'You have a task due soon',
id: 1,
schedule: { at: new Date(Date.now() + 3000) },
sound: null,
attachments: null,
actionTypeId: '',
extra: null,
},
],
});
}
}>Test Notification </Button>
<Typography level='h3'>Custom Notification</Typography>
<Divider />
<Typography level='body-md'>Notificaiton through other platform like Telegram or Pushover</Typography>
<FormControl
orientation="horizontal"
sx={{ width: 400, justifyContent: 'space-between' }}
>
<div>
<FormLabel>Custom Notification</FormLabel>
<FormHelperText sx={{ mt: 0 }}>Receive notification on other platform</FormHelperText>
</div>
<Switch
checked={chatID !== 0}
onClick={(event) =>{
event.preventDefault()
if (chatID !== 0){
setChatID(0)
}
else{
setChatID('')
UpdateUserDetails({
chatID: Number(0),
}).then(resp => {
resp.json().then(data => {
setUserProfile(data)
})
})
}
setNotificationTarget('0')
handleSave()
}
}
color={chatID!==0 ? 'success' : 'neutral'}
variant={chatID!==0 ? 'solid' : 'outlined'}
endDecorator={chatID!==0 ? 'On' : 'Off'}
slotProps={{
endDecorator: {
sx: {
minWidth: 24,
},
},
}}
/>
</FormControl>
{chatID !== 0&& (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Select
value={notificationTarget}
sx={{ maxWidth: '200px' }}
@ -141,6 +431,16 @@ const NotificationSetting = () => {
>
Save
</Button>
</Box>
)}
<Snackbar open={isSnackbarOpen} autoHideDuration={8000} onClose={() => setIsSnackbarOpen(false)} endDecorator={<IconButton size='md' onClick={() => setIsSnackbarOpen(false)}><Close/></IconButton>}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Typography level='title-md'>Permission Denied</Typography>
<Typography level='body-md'>
You have denied the permission to receive notification on this device. Please enable it in your device settings
</Typography>
</div>
</Snackbar>
</div>
)
}