From db6e3abbd03599e7692e70f55ecc54acaff3c412 Mon Sep 17 00:00:00 2001 From: itschip Date: Sat, 27 Jan 2024 16:50:37 +0100 Subject: [PATCH] refactor(apps/phone): notification ui --- apps/phone/src/Phone.tsx | 233 ++++++++++-------- .../src/apps/dialer/components/DialerApp.tsx | 3 +- .../apps/twitter/components/TwitterApp.tsx | 2 +- apps/phone/src/lib/RecoilDebugObserver.tsx | 7 +- apps/phone/src/lib/RecoilRootManager.tsx | 6 +- .../os/call/components/CallNotification.tsx | 6 +- .../os/call/hooks/useCallNotifications.tsx | 4 +- .../components/NotificationBase.tsx | 130 +++++----- .../components/calls/CallNotificationBase.tsx | 191 +++++++------- .../snackbar/providers/SnackbarProvider.tsx | 5 +- 10 files changed, 300 insertions(+), 287 deletions(-) diff --git a/apps/phone/src/Phone.tsx b/apps/phone/src/Phone.tsx index e59727f44..28b37ac88 100644 --- a/apps/phone/src/Phone.tsx +++ b/apps/phone/src/Phone.tsx @@ -1,130 +1,149 @@ -import React, { Dispatch, Fragment, SetStateAction, useEffect } from 'react'; +import React, {Dispatch, Fragment, SetStateAction, useEffect} from 'react'; import './Phone.css'; -import { Route } from 'react-router-dom'; -import { CallModal } from '@os/call/components/CallModal'; -import { HomeApp } from './apps/home/components/Home'; -import { NotificationBar } from '@os/new-notifications/components/NotificationBar'; -import { Navigation } from '@os/navigation-bar/components/Navigation'; -import { useSimcardService } from '@os/simcard/hooks/useSimcardService'; -import { usePhoneService } from '@os/phone/hooks/usePhoneService'; -import { useApps } from '@os/apps/hooks/useApps'; -import { useTwitterService } from './apps/twitter/hooks/useTwitterService'; -import { useMarketplaceService } from './apps/marketplace/hooks/useMarketplaceService'; -import { useMessagesService } from './apps/messages/hooks/useMessageService'; -import { useSettings } from './apps/settings/hooks/useSettings'; -import { useCallService } from '@os/call/hooks/useCallService'; -import { useDialService } from './apps/dialer/hooks/useDialService'; -import { useMatchService } from './apps/match/hooks/useMatchService'; +import {Route} from 'react-router-dom'; +import {CallModal} from '@os/call/components/CallModal'; +import {HomeApp} from './apps/home/components/Home'; +import {NotificationBar} from '@os/new-notifications/components/NotificationBar'; +import {Navigation} from '@os/navigation-bar/components/Navigation'; +import {useSimcardService} from '@os/simcard/hooks/useSimcardService'; +import {usePhoneService} from '@os/phone/hooks/usePhoneService'; +import {useApps} from '@os/apps/hooks/useApps'; +import {useTwitterService} from './apps/twitter/hooks/useTwitterService'; +import {useMarketplaceService} from './apps/marketplace/hooks/useMarketplaceService'; +import {useMessagesService} from './apps/messages/hooks/useMessageService'; +import {useSettings} from './apps/settings/hooks/useSettings'; +import {useCallService} from '@os/call/hooks/useCallService'; +import {useDialService} from './apps/dialer/hooks/useDialService'; +import {useMatchService} from './apps/match/hooks/useMatchService'; import InjectDebugData from './os/debug/InjectDebugData'; -import { NotificationAlert } from '@os/notifications/components/NotificationAlert'; -import { useCallModal } from '@os/call/hooks/useCallModal'; +import {NotificationAlert} from '@os/notifications/components/NotificationAlert'; +import {useCallModal} from '@os/call/hooks/useCallModal'; import WindowSnackbar from './ui/components/WindowSnackbar'; -import { useTranslation } from 'react-i18next'; -import { PhoneEvents } from '@typings/phone'; +import {useTranslation} from 'react-i18next'; +import {PhoneEvents} from '@typings/phone'; import PhoneWrapper from './PhoneWrapper'; import DefaultConfig from '../../../config.default.json'; -import { TopLevelErrorComponent } from '@ui/components/TopLevelErrorComponent'; -import { useConfig } from '@os/phone/hooks/useConfig'; -import { useContactsListener } from './apps/contacts/hooks/useContactsListener'; -import { useNoteListener } from './apps/notes/hooks/useNoteListener'; -import { PhoneSnackbar } from '@os/snackbar/components/PhoneSnackbar'; -import { useInvalidSettingsHandler } from './apps/settings/hooks/useInvalidSettingsHandler'; -import { useKeyboardService } from '@os/keyboard/hooks/useKeyboardService'; -import { useExternalApps } from '@common/hooks/useExternalApps'; -import { useTheme } from '@mui/material'; -import { useDarkchatService } from './apps/darkchat/hooks/useDarkchatService'; -import { useNotificationListener } from '@os/new-notifications/useNotificationListener'; -import { useSystemNotificationListener } from '@os/new-notifications/components/system/useSystemNotificationListener'; -import { useNotificationBarListener } from '@os/new-notifications/useNotificationBarListener'; +import {TopLevelErrorComponent} from '@ui/components/TopLevelErrorComponent'; +import {useConfig} from '@os/phone/hooks/useConfig'; +import {useContactsListener} from './apps/contacts/hooks/useContactsListener'; +import {useNoteListener} from './apps/notes/hooks/useNoteListener'; +import {PhoneSnackbar} from '@os/snackbar/components/PhoneSnackbar'; +import {useInvalidSettingsHandler} from './apps/settings/hooks/useInvalidSettingsHandler'; +import {useKeyboardService} from '@os/keyboard/hooks/useKeyboardService'; +import {useExternalApps} from '@common/hooks/useExternalApps'; +import {useTheme} from '@mui/material'; +import {useDarkchatService} from './apps/darkchat/hooks/useDarkchatService'; +import {useNotificationListener} from '@os/new-notifications/useNotificationListener'; +import {useSystemNotificationListener} from '@os/new-notifications/components/system/useSystemNotificationListener'; +import {useNotificationBarListener} from '@os/new-notifications/useNotificationBarListener'; +import {ActiveCall, CallEvents} from "@typings/call"; +import {NotificationEvents, SystemNotificationDTO} from "@typings/notifications"; interface PhoneProps { - notiRefCB: Dispatch>; + notiRefCB: Dispatch>; } -const Phone: React.FC = ({ notiRefCB }) => { - const { i18n } = useTranslation(); +const Phone: React.FC = ({notiRefCB}) => { + const {i18n} = useTranslation(); - const { apps } = useApps(); - const [settings] = useSettings(); - const theme = useTheme(); + const {apps} = useApps(); + const [settings] = useSettings(); + const theme = useTheme(); - // Set language from local storage - // This will only trigger on first mount & settings changes - useEffect(() => { - i18n.changeLanguage(settings.language.value).catch((e) => console.error(e)); - }, [i18n, settings.language]); + // Set language from local storage + // This will only trigger on first mount & settings changes + useEffect(() => { + i18n.changeLanguage(settings.language.value).catch((e) => console.error(e)); + }, [i18n, settings.language]); - useEffect(() => { - if (settings.theme.value === 'taso-dark') { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - }, [settings.theme.value]); + useEffect(() => { + if (settings.theme.value === 'taso-dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [settings.theme.value]); - useConfig(); - useKeyboardService(); - usePhoneService(); - useSimcardService(); - useNotificationListener(); - useSystemNotificationListener(); - useNotificationBarListener(); - useTwitterService(); - useMatchService(); - useMarketplaceService(); - useMessagesService(); - useContactsListener(); - useNoteListener(); - useCallService(); - useDialService(); - useDarkchatService(); - useInvalidSettingsHandler(); + useConfig(); + useKeyboardService(); + usePhoneService(); + useSimcardService(); + useNotificationListener(); + useSystemNotificationListener(); + useNotificationBarListener(); + useTwitterService(); + useMatchService(); + useMarketplaceService(); + useMessagesService(); + useContactsListener(); + useNoteListener(); + useCallService(); + useDialService(); + useDarkchatService(); + useInvalidSettingsHandler(); - const externalApps = useExternalApps(); + const externalApps = useExternalApps(); - const { modal: callModal } = useCallModal(); + const {modal: callModal} = useCallModal(); - return ( -
- - - - -
- <> - - {callModal && } - {apps.map((App) => ( - {!App.isDisabled && } - ))} + return ( +
+ + + + +
+ <> + + {callModal && } + {apps.map((App) => ( + {!App.isDisabled && } + ))} - {externalApps.map((App) => ( - - - - ))} - - - -
- -
-
-
- ); + {externalApps.map((App) => ( + + + + ))} + + + +
+ +
+
+
+ ); }; InjectDebugData([ - { - app: 'PHONE', - method: PhoneEvents.SET_CONFIG, - data: DefaultConfig, - }, - { - app: 'PHONE', - method: PhoneEvents.SET_VISIBILITY, - data: true, - }, + { + app: 'PHONE', + method: PhoneEvents.SET_CONFIG, + data: DefaultConfig, + }, + { + app: 'PHONE', + method: PhoneEvents.SET_VISIBILITY, + data: true, + }, ]); +InjectDebugData([ + { + app: 'SYSTEM', + method: NotificationEvents.CREATE_SYSTEM_NOTIFICATION, + data: { + content: "This is a test notification", + controls: true, + duration: 5000, + keepOpen: true, + onCancel: () => console.log("Cancelled"), + onConfirm: () => console.log("Confirmed"), + uniqId: "test", + secondaryTitle: "Test Notification", + } + } +]) + export default Phone; diff --git a/apps/phone/src/apps/dialer/components/DialerApp.tsx b/apps/phone/src/apps/dialer/components/DialerApp.tsx index d29ced6c1..27ca80d56 100644 --- a/apps/phone/src/apps/dialer/components/DialerApp.tsx +++ b/apps/phone/src/apps/dialer/components/DialerApp.tsx @@ -8,6 +8,7 @@ import DialerNavBar from './DialerNavBar'; import { ContactList } from '../../contacts/components/List/ContactList'; import { DialerThemeProvider } from '../providers/DialerThemeProvider'; import { LoadingSpinner } from '@ui/components/LoadingSpinner'; +import InjectDebugData from "@os/debug/InjectDebugData"; export const DialerApp: React.FC = () => { return ( @@ -32,4 +33,4 @@ export const DialerApp: React.FC = () => { ); -}; +}; \ No newline at end of file diff --git a/apps/phone/src/apps/twitter/components/TwitterApp.tsx b/apps/phone/src/apps/twitter/components/TwitterApp.tsx index 42f204dfa..810c526f1 100644 --- a/apps/phone/src/apps/twitter/components/TwitterApp.tsx +++ b/apps/phone/src/apps/twitter/components/TwitterApp.tsx @@ -85,7 +85,7 @@ InjectDebugData( retweetIdentifier: '', avatar_url: '', id: 116, - message: 'Go is better!', + message: 'Go is better! Here is why: Go is a statically typed, compiled language in the tradition of C, with memory safety, garbage collection, structural typing, and CSP-style concurrency. The compiler, tools, and source code are all free and open source.', createdAt: '2021-12-01 00:42:03', updatedAt: '2021-12-01 00:42:03', identifier: '', diff --git a/apps/phone/src/lib/RecoilDebugObserver.tsx b/apps/phone/src/lib/RecoilDebugObserver.tsx index d9357ed06..113405824 100644 --- a/apps/phone/src/lib/RecoilDebugObserver.tsx +++ b/apps/phone/src/lib/RecoilDebugObserver.tsx @@ -2,7 +2,12 @@ import React, { useEffect } from 'react'; import { useRecoilSnapshot } from 'recoil'; import defaultConfig from './../config/default.json'; -export const RecoilDebugObserver: React.FC = ({ children }) => { + +interface RecoilDebugObserverProps { + children: React.ReactNode; +} + +export const RecoilDebugObserver: React.FC = ({ children }) => { const snapshot = useRecoilSnapshot(); useEffect(() => { diff --git a/apps/phone/src/lib/RecoilRootManager.tsx b/apps/phone/src/lib/RecoilRootManager.tsx index 08bd1ce1e..6b93e4fe1 100644 --- a/apps/phone/src/lib/RecoilRootManager.tsx +++ b/apps/phone/src/lib/RecoilRootManager.tsx @@ -10,7 +10,11 @@ import RecoilCacheReset from './RecoilCacheReset'; // a refresh // This is liable to memory leaky behavior in extreme cases. -export const RecoilRootManager: React.FC = ({ children }) => { +interface RecoilRootManagerProps { + children: React.ReactNode; +} + +export const RecoilRootManager: React.FC = ({ children }) => { const [charState, setCharState] = useState(0); useNuiEvent('PHONE', PhoneEvents.UNLOAD_CHARACTER, () => { diff --git a/apps/phone/src/os/call/components/CallNotification.tsx b/apps/phone/src/os/call/components/CallNotification.tsx index 61978db97..1c94a5964 100644 --- a/apps/phone/src/os/call/components/CallNotification.tsx +++ b/apps/phone/src/os/call/components/CallNotification.tsx @@ -11,7 +11,11 @@ const StyledControls = styled(Box)(({ theme }) => ({ right: '0', })); -export const CallNotification: React.FC = ({ children }) => ( +interface CallNotificationProps { + children: React.ReactNode; +} + +export const CallNotification: React.FC = ({ children }) => ( {children} diff --git a/apps/phone/src/os/call/hooks/useCallNotifications.tsx b/apps/phone/src/os/call/hooks/useCallNotifications.tsx index 91d65a3a6..071036bd5 100644 --- a/apps/phone/src/os/call/hooks/useCallNotifications.tsx +++ b/apps/phone/src/os/call/hooks/useCallNotifications.tsx @@ -5,8 +5,8 @@ import { ActiveCall } from '@typings/call'; import { useApp } from '@os/apps/hooks/useApps'; import { useNotifications } from '@os/notifications/hooks/useNotifications'; import { CallNotification } from '../components/CallNotification'; -import { useContactActions } from '../../../apps/contacts/hooks/useContactActions'; -import { useContacts } from '../../../apps/contacts/hooks/state'; +import { useContactActions } from '@apps/contacts/hooks/useContactActions'; +import { useContacts } from '@apps/contacts/hooks/state'; const NOTIFICATION_ID = 'call:current'; diff --git a/apps/phone/src/os/new-notifications/components/NotificationBase.tsx b/apps/phone/src/os/new-notifications/components/NotificationBase.tsx index d16847295..87eb0c055 100644 --- a/apps/phone/src/os/new-notifications/components/NotificationBase.tsx +++ b/apps/phone/src/os/new-notifications/components/NotificationBase.tsx @@ -1,87 +1,71 @@ -import { Box, Typography } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { IApp } from '@os/apps/config/apps'; -import { SnackbarContent, CustomContentProps } from 'notistack'; -import React, { forwardRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; -import { useNotification } from '../useNotification'; - -const StyledMessage = styled('div')({ - color: 'white', - fontSize: 16, - textOverflow: 'ellipsis', - display: '-webkit-box', - overflow: 'hidden', - boxOrient: 'vertical', - lineClamp: 2, -}); +import {IApp} from '@os/apps/config/apps'; +import {SnackbarContent, CustomContentProps} from 'notistack'; +import React, {forwardRef} from 'react'; +import {useTranslation} from 'react-i18next'; +import {useHistory} from 'react-router-dom'; +import {useNotification} from '../useNotification'; +import {cn} from "@npwd/keyos"; interface NotificationBaseProps extends CustomContentProps { - app: IApp; - secondaryTitle?: string; - path?: string; - onClick?: () => void; + app: IApp; + secondaryTitle?: string; + path?: string; + onClick?: () => void; } export type NotificationBaseComponent = React.FC; -const StyledSnackbar = styled(SnackbarContent)(({ theme }) => ({ - padding: '14px 16px', - display: 'flex', - background: theme.palette.background.paper, - borderRadius: '12px !important', - boxShadow: 'rgba(0, 0, 0, 0.35) 0px 5px 15px', -})); - const NotificationBase = forwardRef((props, ref) => { - const { markAsRead } = useNotification(); - const { app, message, secondaryTitle, path, onClick } = props; - const [t] = useTranslation(); - const history = useHistory(); + const {markAsRead} = useNotification(); + const {app, message, secondaryTitle, path, onClick} = props; + const [t] = useTranslation(); + const history = useHistory(); - const handleNotisClick = () => { - path && !onClick ? history.push(path) : onClick(); - markAsRead(props.id.toString()); - }; + const handleNotisClick = () => { + path && !onClick ? history.push(path) : onClick(); + markAsRead(props.id.toString()); + }; - if (!app) { - console.error('App was not found. Could not render notification.'); - console.error( - 'If you are using an external app, make sure it is started before NPWD and that you pass the correct app id to the notification.', - ); - return null; - } + if (!app) { + console.error('App was not found. Could not render notification.'); + console.error( + 'If you are using an external app, make sure it is started before NPWD and that you pass the correct app id to the notification.', + ); + return null; + } + + if (!app.NotificationIcon) { + console.warn('App does not have a notification icon'); + } - if (!app.NotificationIcon) { - console.warn('App does not have a notification icon'); - } + console.log("app bg", app.backgroundColor) - return ( - - - - {app.NotificationIcon && } - - - {t(app.nameLocale)} - - - {secondaryTitle} - - - - {message} - - - ); +
+
+ {app.NotificationIcon && } +
+
+ {t(app.nameLocale)} +
+
+

{secondaryTitle}

+
+
+
+

{message}

+
+ + ); }); -export default NotificationBase; +export default NotificationBase; \ No newline at end of file diff --git a/apps/phone/src/os/new-notifications/components/calls/CallNotificationBase.tsx b/apps/phone/src/os/new-notifications/components/calls/CallNotificationBase.tsx index ebfa88e8e..321209eff 100644 --- a/apps/phone/src/os/new-notifications/components/calls/CallNotificationBase.tsx +++ b/apps/phone/src/os/new-notifications/components/calls/CallNotificationBase.tsx @@ -1,30 +1,19 @@ -import styled from '@emotion/styled'; import CallEnd from '@mui/icons-material/CallEnd'; -import Call from '@mui/icons-material/Call'; -import { Avatar, Box, IconButton, Typography } from '@mui/material'; -import { green, red } from '@mui/material/colors'; -import { SnackbarContent, CustomContentProps } from 'notistack'; -import React, { forwardRef, useMemo } from 'react'; -import { useCurrentCallValue } from '@os/call/hooks/state'; -import { useCall } from '@os/call/hooks/useCall'; +import {Avatar} from '@mui/material'; +import {SnackbarContent, CustomContentProps} from 'notistack'; +import React, {forwardRef, useMemo} from 'react'; +import {useCurrentCallValue} from '@os/call/hooks/state'; +import {useCall} from '@os/call/hooks/useCall'; import useTimer from '@os/call/hooks/useTimer'; -import { useTranslation } from 'react-i18next'; -import { useContactActions } from '@apps/contacts/hooks/useContactActions'; - -const StyledSnackbar = styled(SnackbarContent)(({ theme }) => ({ - padding: '14px 16px', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - background: theme.palette.background.paper, - borderRadius: '12px !important', - boxShadow: 'rgba(0, 0, 0, 0.35) 0px 5px 15px', -})); +import {useTranslation} from 'react-i18next'; +import {useContactActions} from '@apps/contacts/hooks/useContactActions'; +import {NPWDButton} from "@npwd/keyos"; +import {Phone} from "lucide-react"; interface CallNotificationBaseProps extends CustomContentProps { - title: string; - transmitter: string; - receiver: string; + title: string; + transmitter: string; + receiver: string; } export type CallNotificationBaseComponent = React.FC; @@ -32,90 +21,94 @@ export type CallNotificationBaseComponent = React.FC; const formatTime = (time: number) => (time < 10 ? `0${time}` : time); export const CallNotificationBase = forwardRef( - (props, ref) => { - const { endCall, acceptCall, rejectCall } = useCall(); - const { transmitter, receiver } = props; - const call = useCurrentCallValue(); - const { minutes, seconds, startTimer, resetTimer } = useTimer(); + (props, ref) => { + const {endCall, acceptCall, rejectCall} = useCall(); + const {transmitter, receiver} = props; + const call = useCurrentCallValue(); + const {minutes, seconds, startTimer, resetTimer} = useTimer(); - const { getDisplayByNumber, getPictureByNumber } = useContactActions(); + const {getPictureByNumber} = useContactActions(); - const { t } = useTranslation(); + const {t} = useTranslation(); - const RECEIVER_TEXT = useMemo( - () => - call?.is_accepted - ? receiver - : `${t('DIALER.MESSAGES.CALLING', { - transmitter: receiver, - })}`, - [call.is_accepted, receiver], - ); + const RECEIVER_TEXT = useMemo( + () => + call?.is_accepted + ? receiver + : `${t('DIALER.MESSAGES.CALLING', { + transmitter: receiver, + })}`, + [call.is_accepted, receiver], + ); - const handleAcceptCall = () => { - acceptCall(); + const handleAcceptCall = () => { + acceptCall(); - resetTimer(); - startTimer(); - }; + resetTimer(); + startTimer(); + }; - const handleEndOrRejectCall = () => { - if (!call.is_accepted && !call.isTransmitter) { - rejectCall(); - } else { - endCall(); - } - resetTimer(); - }; + const handleEndOrRejectCall = () => { + if (!call.is_accepted && !call.isTransmitter) { + rejectCall(); + } else { + endCall(); + } + resetTimer(); + }; - if (!call) { - return null; - } + if (!call) { + return null; + } - const getDisplayAvatar = () => { - return call.isTransmitter - ? getPictureByNumber(call.receiver) - : getPictureByNumber(call?.transmitter); - }; + const getDisplayAvatar = () => { + return call.isTransmitter + ? getPictureByNumber(call.receiver) + : getPictureByNumber(call?.transmitter); + }; - return ( - - - - - - - {call?.isTransmitter ? ( - {RECEIVER_TEXT} - ) : ( - {transmitter} - )} - - - - {call?.is_accepted && ( - - {`${formatTime(minutes)}:${formatTime(seconds)}`} - - )} - {!call?.isTransmitter && !call?.is_accepted && ( - - - - )} - - - - - - ); - }, +
+
+ +
+
+ {call?.isTransmitter ? ( +

{RECEIVER_TEXT}

+ ) : ( +

{transmitter}

+ )} +
+
+
+ {call?.is_accepted && ( +

+ {`${formatTime(minutes)}:${formatTime(seconds)}`} +

+ )} + {!call?.isTransmitter && !call?.is_accepted && ( + + + + )} + + + +
+ + ); + }, ); diff --git a/apps/phone/src/os/snackbar/providers/SnackbarProvider.tsx b/apps/phone/src/os/snackbar/providers/SnackbarProvider.tsx index 557fab0ea..3276a8613 100644 --- a/apps/phone/src/os/snackbar/providers/SnackbarProvider.tsx +++ b/apps/phone/src/os/snackbar/providers/SnackbarProvider.tsx @@ -3,7 +3,10 @@ import { IAlert } from '../hooks/useSnackbar'; export const SnackbarContext = createContext(null); -const SnackbarProvider: React.FC = ({ children }) => { +interface SnackbarProviderProps { + children: React.ReactNode; +} +const SnackbarProvider: React.FC = ({ children }) => { const [open, setOpen] = useState(false); const [alert, setAlert] = useState(null);