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:
parent
1e7b47e783
commit
bcd32a8616
130 changed files with 6699 additions and 880 deletions
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue