diff --git a/app/electron/src/store.ts b/app/electron/src/store.ts index e5558246..c344349f 100644 --- a/app/electron/src/store.ts +++ b/app/electron/src/store.ts @@ -17,7 +17,7 @@ type StoreProps = { * This also ensures that we can force calling the store safely. Though I have switched the names to safeGet and safeSet to make it more clear. */ class SafeStore< - T extends Record = Record, + T extends Record = Record > { private store: ElectronStore; constructor(props: Options) { diff --git a/app/renderer/src/assets/audios/notification/clock-alarm.mp3 b/app/renderer/src/assets/audios/notification/clock-alarm.mp3 new file mode 100644 index 00000000..53c2713d Binary files /dev/null and b/app/renderer/src/assets/audios/notification/clock-alarm.mp3 differ diff --git a/app/renderer/src/assets/audios/notification/pomodoro.mp3 b/app/renderer/src/assets/audios/notification/pomodoro.mp3 new file mode 100644 index 00000000..c355ddb0 Binary files /dev/null and b/app/renderer/src/assets/audios/notification/pomodoro.mp3 differ diff --git a/app/renderer/src/assets/audios/notification/treasure.mp3 b/app/renderer/src/assets/audios/notification/treasure.mp3 new file mode 100644 index 00000000..ff101229 Binary files /dev/null and b/app/renderer/src/assets/audios/notification/treasure.mp3 differ diff --git a/app/renderer/src/assets/audios/notification/trumpets.mp3 b/app/renderer/src/assets/audios/notification/trumpets.mp3 new file mode 100644 index 00000000..68dad2d8 Binary files /dev/null and b/app/renderer/src/assets/audios/notification/trumpets.mp3 differ diff --git a/app/renderer/src/components/Dropdown.tsx b/app/renderer/src/components/Dropdown.tsx new file mode 100644 index 00000000..05a36aa4 --- /dev/null +++ b/app/renderer/src/components/Dropdown.tsx @@ -0,0 +1,38 @@ +import { SVG } from "components"; +import React, { useState } from "react"; +import { + StyledDropdown, + StyledDropdownContent, + StyledDropdownHeading, +} from "styles"; + +type Props = { + children?: React.ReactNode; +}; + +// Todo, convert this into a dropdown menu instead of Radio buttons +const Dropdown: React.FC = ({ children }) => { + const [open, setOpen] = useState(false); + + const toggleDropdown = () => { + setOpen((prevState) => !prevState); + }; + + return ( + + + Notification Sound + + + {open && ( + {children} + )} + + ); +}; + +export default React.memo(Dropdown); diff --git a/app/renderer/src/components/index.ts b/app/renderer/src/components/index.ts index fc701198..575f6fbc 100644 --- a/app/renderer/src/components/index.ts +++ b/app/renderer/src/components/index.ts @@ -22,6 +22,8 @@ export { default as Radio } from "./Radio"; export { default as Collapse } from "./Collapse"; +export { default as Dropdown } from "./Dropdown"; + export { default as Alert } from "./Alert"; export { default as Updater } from "./Updater"; export { default as NavNotify } from "./NavNotify"; diff --git a/app/renderer/src/contexts/CounterContext.tsx b/app/renderer/src/contexts/CounterContext.tsx index 9e9f9c96..976be80f 100644 --- a/app/renderer/src/contexts/CounterContext.tsx +++ b/app/renderer/src/contexts/CounterContext.tsx @@ -44,6 +44,7 @@ const CounterProvider: React.FC = ({ children }) => { { icon: notificationIcon, mute: !settings.notificationSoundOn, + notificationSound: settings.notificationSound, }, settings.notificationType !== "none" ); diff --git a/app/renderer/src/hooks/useNotification.ts b/app/renderer/src/hooks/useNotification.ts index db3e22f2..4afa9157 100644 --- a/app/renderer/src/hooks/useNotification.ts +++ b/app/renderer/src/hooks/useNotification.ts @@ -1,7 +1,12 @@ import bell from "assets/audios/notification-bell.wav"; +import pomodoro from "assets/audios/notification/pomodoro.mp3"; +import treasure from "assets/audios/notification/treasure.mp3"; +import trumpets from "assets/audios/notification/trumpets.mp3"; +import { NotificationSounds } from "store/settings/types"; type OptionProps = { mute?: boolean; + notificationSound: NotificationSounds; } & NotificationOptions; export const useNotification = ( @@ -23,7 +28,20 @@ export const useNotification = ( // in all Operating System if (!constantOptions?.mute) { - new Audio(bell).play().catch((e) => { + let sound; + + switch (constantOptions?.notificationSound) { + case NotificationSounds.POMODORO: + sound = pomodoro; + break; + case NotificationSounds.TRUMPETS: + sound = trumpets; + break; + default: + sound = bell; + } + + new Audio(sound).play().catch((e) => { console.warn("There was a problem playing sound", e); }); diff --git a/app/renderer/src/routes/Settings/FeatureSection.tsx b/app/renderer/src/routes/Settings/FeatureSection.tsx index 89bbf89e..ed6b5ec8 100644 --- a/app/renderer/src/routes/Settings/FeatureSection.tsx +++ b/app/renderer/src/routes/Settings/FeatureSection.tsx @@ -5,6 +5,7 @@ import { setEnableStrictMode, setEnableProgressAnimation, setNotificationType, + setNotificationSound, setEnableFullscreenBreak, setUseNativeTitlebar, setAutoStartWorkTime, @@ -14,12 +15,21 @@ import { setEnableCompactMode, setOpenAtLogin, } from "store"; -import { Toggler, TogglerProps, Collapse, Radio } from "components"; +import { + Toggler, + TogglerProps, + Collapse, + Radio, + Dropdown, +} from "components"; import { ThemeContext } from "contexts"; import SettingSection from "./SettingSection"; import { detectOS } from "utils"; -import { NotificationTypes } from "store/settings/types"; +import { + NotificationTypes, + NotificationSounds, +} from "store/settings/types"; const FeatureSection: React.FC = () => { const settings = useAppSelector((state) => state.settings); @@ -149,6 +159,15 @@ const FeatureSection: React.FC = () => { [dispatch] ); + const onChangeNotificationSound = useCallback( + (e: React.ChangeEvent) => { + dispatch( + setNotificationSound(e.target.value as NotificationSounds) + ); + }, + [dispatch] + ); + return ( {featureList.map( @@ -193,6 +212,38 @@ const FeatureSection: React.FC = () => { onChange={onChangeNotificationProps} /> + + + + + ); }; diff --git a/app/renderer/src/store/settings/defaultSettings.ts b/app/renderer/src/store/settings/defaultSettings.ts index d8c2de55..d867de5d 100644 --- a/app/renderer/src/store/settings/defaultSettings.ts +++ b/app/renderer/src/store/settings/defaultSettings.ts @@ -1,4 +1,8 @@ -import { NotificationTypes, SettingTypes } from "./types"; +import { + NotificationTypes, + NotificationSounds, + SettingTypes, +} from "./types"; import { detectOS, isPreferredDark } from "utils"; export const defaultSettings: Readonly = Object.freeze({ @@ -12,6 +16,7 @@ export const defaultSettings: Readonly = Object.freeze({ enableVoiceAssistance: false, notificationSoundOn: true, notificationType: NotificationTypes.NONE, + notificationSound: NotificationSounds.DEFAULT, closeToTray: true, minimizeToTray: false, autoStartWorkTime: false, diff --git a/app/renderer/src/store/settings/index.ts b/app/renderer/src/store/settings/index.ts index a7de5d0b..d83f0b56 100644 --- a/app/renderer/src/store/settings/index.ts +++ b/app/renderer/src/store/settings/index.ts @@ -83,6 +83,13 @@ const settingsSlice = createSlice({ state.notificationType = action.payload; }, + setNotificationSound( + state, + action: SettingsPayload<"notificationSound"> + ) { + state.notificationSound = action.payload; + }, + setCloseToTray(state, action: SettingsPayload<"closeToTray">) { state.closeToTray = action.payload; }, @@ -125,6 +132,7 @@ export const { setIgnoreUpdate, setMinimizeToTray, setNotificationType, + setNotificationSound, setOpenAtLogin, setUseNativeTitlebar, toggleNotificationSound, diff --git a/app/renderer/src/store/settings/types.ts b/app/renderer/src/store/settings/types.ts index 90bf3443..5ccfc62a 100644 --- a/app/renderer/src/store/settings/types.ts +++ b/app/renderer/src/store/settings/types.ts @@ -15,9 +15,17 @@ export type SettingTypes = { minimizeToTray: boolean; autoStartWorkTime: boolean; notificationType: NotificationTypes; + notificationSound: NotificationSounds; openAtLogin: boolean; }; +export const enum NotificationSounds { + DEFAULT = "default", // Windows sound.... + TREASURE = "treasure", // https://pixabay.com/sound-effects/short-success-sound-glockenspiel-treasure-video-game-6346/ + TRUMPETS = "trumpets", //https://pixabay.com/sound-effects/success-fanfare-trumpets-6185/ + POMODORO = "pomodoro", // https://pixabay.com/sound-effects/tomato-squishwet-103934/ +} + export const enum NotificationTypes { NONE = "none", NORMAL = "normal", diff --git a/app/renderer/src/styles/components/dropdown.ts b/app/renderer/src/styles/components/dropdown.ts new file mode 100644 index 00000000..ec71a2d3 --- /dev/null +++ b/app/renderer/src/styles/components/dropdown.ts @@ -0,0 +1,90 @@ +import styled from "styled-components/macro"; + +export const StyledDropdown = styled.div` + width: 100%; + min-height: 4rem; + + display: grid; + grid-template-rows: 4rem; +`; + +export const StyledDropdownHeading = styled.h4<{ open?: boolean }>` + width: 100%; + height: 100%; + + display: flex; + align-items: center; + justify-content: space-between; + + color: var(--color-body-text); + font-weight: 400; + border: none; + background-color: transparent; + + &:focus { + color: var(--color-primary); + } + + & > svg { + width: 1.4rem; + height: 1.4rem; + + margin-right: 0.8rem; + + transform: ${(p) => p.open && "rotate(180deg)"}; + transition: transform 180ms ease; + } + + position: relative; + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + + width: 100%; + height: 1px; + + background-color: var(--color-border-secondary); + } +`; + +export const StyledDropdownContent = styled.div` + width: 100%; + height: 4rem; + + display: flex; + align-items: center; + + animation: enter 180ms ease; + + & > label:not(:last-of-type) { + margin-right: 3.2rem; + } + + position: relative; + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + + width: 100%; + height: 1px; + + background-color: var(--color-border-secondary); + } + + @keyframes enter { + 0% { + opacity: 0; + transform: translateY(-1rem); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } +`; diff --git a/app/renderer/src/styles/components/index.ts b/app/renderer/src/styles/components/index.ts index c54af9da..84771e32 100644 --- a/app/renderer/src/styles/components/index.ts +++ b/app/renderer/src/styles/components/index.ts @@ -17,3 +17,4 @@ export * from "./popper"; export * from "./loaders"; export * from "./collapse"; export * from "./alert"; +export * from "./dropdown"; diff --git a/app/renderer/src/typings.d.ts b/app/renderer/src/typings.d.ts index 71a8ca22..8373e06f 100644 --- a/app/renderer/src/typings.d.ts +++ b/app/renderer/src/typings.d.ts @@ -3,6 +3,7 @@ declare module "*.woff2"; declare module "*.png"; declare module "*.jpg"; declare module "*.wav"; +declare module "*.mp3"; declare module "*.mp4"; declare module "*.ogv"; declare module "*.webm";