From 49a232136d214cbb7f737940f06d3cb3f4cc348b Mon Sep 17 00:00:00 2001 From: IrohDW Date: Fri, 13 Dec 2024 15:34:12 -0700 Subject: [PATCH] Mobile support for Home, Video, and Channel pages. DownloadTaskManager.tsx: 1. Fixed bug that made clicking on videos do nothing instead of going to that video's page. 2. It also displays the video title instead of identifier. 3. The menu is a lot wider, so it is far easier to see the full titles of videos VideoPlayer.tsx now persists across sessions whether it was muted and its volume when unmuting. When screen is small, VideoPlayer automatically switches to Mobile Controls. Mobile Controls now show Playback Rate and Fullscreen buttons. DownloadTaskManager.tsx now shows the video title instead of identifier. --- package.json | 2 +- src/components/Playlists/Playlists.tsx | 16 +- .../Publish/PublishVideo/PublishVideo.tsx | 36 +- .../common/Comments/Comments-styles.tsx | 2 +- .../common/ContentButtons/LikeAndDislike.tsx | 4 +- .../common/ContentButtons/SuperLike.tsx | 40 +- src/components/common/DownloadTaskManager.tsx | 313 +++++++-------- .../ListSuperLikes/ListSuperLikeContainer.tsx | 57 ++- .../common/Notifications/Notifications.tsx | 4 +- src/components/common/PopMenu.tsx | 77 ++++ .../common/SuperLikesList/Comment.tsx | 96 +++-- .../common/SuperLikesList/Comments-styles.tsx | 6 +- .../VideoPlayer/Components/MobileControls.tsx | 30 +- .../Components/VideoControls-State.ts | 25 +- .../VideoPlayer/Components/VideoControls.tsx | 9 +- .../common/VideoPlayer/VideoPlayer-State.ts | 44 ++- .../common/VideoPlayer/VideoPlayer.tsx | 1 - .../layout/Navbar/Components/PublishMenu.tsx | 73 ++++ .../layout/Navbar/Components/QtubeLogo.tsx | 40 ++ .../layout/Navbar/Components/UserMenu.tsx | 124 ++++++ .../layout/Navbar/Navbar-styles.tsx | 15 +- src/components/layout/Navbar/Navbar.tsx | 344 ++--------------- src/constants/Misc.ts | 9 + .../PlaylistContent/PlaylistContent.tsx | 365 +++++++++--------- .../VideoContent/ChannelActions.tsx | 85 +--- .../VideoContent/ChannelButtons.tsx | 24 ++ .../ContentPages/VideoContent/ChannelName.tsx | 58 +++ .../VideoContent/VideoActionsBar.tsx | 70 ++-- .../VideoContent/VideoContent-styles.tsx | 13 +- .../VideoContent/VideoContent.tsx | 131 ++++--- src/pages/Home/Components/SearchSidebar.tsx | 29 +- .../Home/Components/VideoList-styles.tsx | 13 +- src/pages/Home/Components/VideoList.tsx | 1 - src/pages/Home/Home.tsx | 133 +++---- src/state/features/persistSlice.ts | 12 + 35 files changed, 1265 insertions(+), 1036 deletions(-) create mode 100644 src/components/common/PopMenu.tsx create mode 100644 src/components/layout/Navbar/Components/PublishMenu.tsx create mode 100644 src/components/layout/Navbar/Components/QtubeLogo.tsx create mode 100644 src/components/layout/Navbar/Components/UserMenu.tsx create mode 100644 src/pages/ContentPages/VideoContent/ChannelButtons.tsx create mode 100644 src/pages/ContentPages/VideoContent/ChannelName.tsx diff --git a/package.json b/package.json index 90535ef..0e9e6af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "qtube", "private": true, - "version": "2.0.0", + "version": "2.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/Playlists/Playlists.tsx b/src/components/Playlists/Playlists.tsx index 0bf6ddc..d514927 100644 --- a/src/components/Playlists/Playlists.tsx +++ b/src/components/Playlists/Playlists.tsx @@ -1,10 +1,11 @@ import React from "react"; +import { smallScreenSizeString } from "../../constants/Misc.ts"; import { CardContentContainerComment } from "../common/Comments/Comments-styles"; import { CrowdfundSubTitle, CrowdfundSubTitleRow, } from "../Publish/PublishVideo/PublishVideo-styles.tsx"; -import { Box, Typography, useTheme } from "@mui/material"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { useNavigate } from "react-router-dom"; export const Playlists = ({ @@ -13,7 +14,8 @@ export const Playlists = ({ onClick, }) => { const theme = useTheme(); - const navigate = useNavigate(); + const isScreenSmall = !useMediaQuery(`(min-width:700px)`); + const videoPlayerHeight = "33.75vw"; // This is videoplayer width * 9/16 (inverse of aspect ratio) return ( {playlistData?.videos?.map((vid, index) => { - const isCurrentVidPlayling = + const isCurrentVidPlaying = vid?.identifier === currentVideoIdentifier; return ( @@ -42,15 +44,15 @@ export const Playlists = ({ display: "flex", gap: "10px", width: "100%", - background: isCurrentVidPlayling && theme.palette.primary.main, + background: isCurrentVidPlaying && theme.palette.primary.main, alignItems: "center", padding: "10px", borderRadius: "5px", - cursor: isCurrentVidPlayling ? "default" : "pointer", + cursor: isCurrentVidPlaying ? "default" : "pointer", userSelect: "none", }} onClick={() => { - if (isCurrentVidPlayling) return; + if (isCurrentVidPlaying) return; onClick(vid.name, vid.identifier); // navigate(`/video/${vid.name}/${vid.identifier}`) }} diff --git a/src/components/Publish/PublishVideo/PublishVideo.tsx b/src/components/Publish/PublishVideo/PublishVideo.tsx index a62642f..372294e 100644 --- a/src/components/Publish/PublishVideo/PublishVideo.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo.tsx @@ -15,8 +15,10 @@ import { Typography, useTheme, } from "@mui/material"; +import { useSignal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; import Compressor from "compressorjs"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { useDropzone } from "react-dropzone"; import { useDispatch, useSelector } from "react-redux"; import ShortUniqueId from "short-unique-id"; @@ -27,6 +29,7 @@ import { } from "../../../constants/Identifiers.ts"; import { maxSize, + menuIconSize, titleFormatter, videoMaxSize, } from "../../../constants/Misc.ts"; @@ -38,7 +41,6 @@ import { import { setNotification } from "../../../state/features/notificationsSlice.ts"; import { RootState } from "../../../state/store.ts"; -import { formatBytes } from "../../../utils/numberFunctions.ts"; import { objectToBase64 } from "../../../utils/PublishFormatter.ts"; import { getFileName } from "../../../utils/stringFunctions.ts"; import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; @@ -66,8 +68,7 @@ import { StyledButton, TimesIcon, } from "./PublishVideo-styles.tsx"; -import { signal, Signal, useSignal } from "@preact/signals-react"; -import { useSignals } from "@preact/signals-react/runtime"; +import VideoLibraryIcon from "@mui/icons-material/VideoLibrary"; export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { @@ -82,13 +83,14 @@ export const toBase64 = (file: File): Promise => const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); -interface NewCrowdfundProps { +interface PublishVideoProps { editId?: string; editContent?: null | { title: string; user: string; coverImage: string | null; }; + afterClose?: () => void; } interface VideoFile { @@ -97,7 +99,11 @@ interface VideoFile { description: string; coverImage?: string; } -export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { +export const PublishVideo = ({ + editId, + editContent, + afterClose, +}: PublishVideoProps) => { const theme = useTheme(); const dispatch = useDispatch(); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); @@ -188,13 +194,9 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { }, }); - // useEffect(() => { - // if (editContent) { - // } - // }, [editContent]); - const onClose = () => { setIsOpen(false); + if (afterClose) afterClose(); }; const search = async () => { @@ -633,12 +635,20 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { {editId ? null : ( } + startIcon={ + + } onClick={() => { setIsOpen(true); }} > - add video + Video )} diff --git a/src/components/common/Comments/Comments-styles.tsx b/src/components/common/Comments/Comments-styles.tsx index 9cda045..34396e2 100644 --- a/src/components/common/Comments/Comments-styles.tsx +++ b/src/components/common/Comments/Comments-styles.tsx @@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({ }); export const CommentsContainer = styled(Box)({ - width: "70%", + width: "90%", maxWidth: "1000px", display: "flex", flexDirection: "column", diff --git a/src/components/common/ContentButtons/LikeAndDislike.tsx b/src/components/common/ContentButtons/LikeAndDislike.tsx index ebf8912..29e97c7 100644 --- a/src/components/common/ContentButtons/LikeAndDislike.tsx +++ b/src/components/common/ContentButtons/LikeAndDislike.tsx @@ -160,10 +160,10 @@ export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => { sx={{ padding: "5px", borderRadius: "7px", - gap: "5px", + gap: "10px", display: "flex", alignItems: "center", - marginRight: "10px", + marginRight: "20px", height: "53px", }} > diff --git a/src/components/common/ContentButtons/SuperLike.tsx b/src/components/common/ContentButtons/SuperLike.tsx index 7e2c4db..60d2b17 100644 --- a/src/components/common/ContentButtons/SuperLike.tsx +++ b/src/components/common/ContentButtons/SuperLike.tsx @@ -1,32 +1,29 @@ -import React, { useEffect, useState } from "react"; import ThumbUpIcon from "@mui/icons-material/ThumbUp"; import { Box, - Button, - Dialog, - DialogActions, DialogContent, - DialogTitle, - FormControl, - Input, InputAdornment, InputLabel, - MenuItem, Modal, - Select, Tooltip, } from "@mui/material"; -import qortImg from "../../../assets/img/qort.png"; -import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx"; +import React, { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { setNotification } from "../../../state/features/notificationsSlice.ts"; import ShortUniqueId from "short-unique-id"; +import qortImg from "../../../assets/img/qort.png"; import { - objectToBase64, - objectToFile, -} from "../../../utils/PublishFormatter.ts"; + FOR, + FOR_SUPER_LIKE, + SUPER_LIKE_BASE, +} from "../../../constants/Identifiers.ts"; import { minPriceSuperlike } from "../../../constants/Misc.ts"; -import { CommentInput } from "../Comments/Comments-styles.tsx"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; +import { RootState } from "../../../state/store.ts"; +import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx"; +import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts"; +import { objectToBase64 } from "../../../utils/PublishFormatter.ts"; +import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts"; +import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx"; import { CrowdfundActionButton, CrowdfundActionButtonRow, @@ -34,16 +31,7 @@ import { NewCrowdfundTitle, Spacer, } from "../../Publish/PublishVideo/PublishVideo-styles.tsx"; -import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.tsx"; -import { RootState } from "../../../state/store.ts"; -import { - FOR, - FOR_SUPER_LIKE, - SUPER_LIKE_BASE, -} from "../../../constants/Identifiers.ts"; -import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx"; -import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts"; -import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts"; +import { CommentInput } from "../Comments/Comments-styles.tsx"; const uid = new ShortUniqueId({ length: 4 }); diff --git a/src/components/common/DownloadTaskManager.tsx b/src/components/common/DownloadTaskManager.tsx index 6ea28d6..c483b2d 100644 --- a/src/components/common/DownloadTaskManager.tsx +++ b/src/components/common/DownloadTaskManager.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect } from "react"; import { Accordion, AccordionDetails, @@ -11,29 +11,28 @@ import { ListItemIcon, Popover, Typography, - useTheme -} from '@mui/material' -import { Movie } from '@mui/icons-material' -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { useLocation, useNavigate } from 'react-router-dom' -import { DownloadingLight } from '../../assets/svgs/DownloadingLight' -import { DownloadedLight } from '../../assets/svgs/DownloadedLight' + useTheme, +} from "@mui/material"; +import { Movie } from "@mui/icons-material"; +import { useSelector } from "react-redux"; +import { RootState } from "../../state/store"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { useLocation, useNavigate } from "react-router-dom"; +import { DownloadingLight } from "../../assets/svgs/DownloadingLight"; +import { DownloadedLight } from "../../assets/svgs/DownloadedLight"; export const DownloadTaskManager: React.FC = () => { - const { downloads } = useSelector((state: RootState) => state.global) - const location = useLocation() - const theme = useTheme() - const [visible, setVisible] = useState(false) - const [hidden, setHidden] = useState(true) - const navigate = useNavigate() + const { downloads } = useSelector((state: RootState) => state.global); + const theme = useTheme(); + const [visible, setVisible] = useState(false); + const [hidden, setHidden] = useState(true); + const navigate = useNavigate(); const [anchorEl, setAnchorEl] = useState(null); - - const [openDownload, setOpenDownload] = useState(false); - + const hashMapVideos = useSelector( + (state: RootState) => state.video.hashMapVideos + ); const handleClick = (event?: React.MouseEvent) => { const target = event?.currentTarget as unknown as HTMLButtonElement | null; setAnchorEl(target); @@ -49,155 +48,159 @@ export const DownloadTaskManager: React.FC = () => { if (visible) { setTimeout(() => { - setHidden(true) - setVisible(false) - }, 3000) + setHidden(true); + setVisible(false); + }, 3000); } - }, [visible]) - + }, [visible]); useEffect(() => { - if (Object.keys(downloads).length === 0) return - setVisible(true) - setHidden(false) - }, [downloads]) + if (Object.keys(downloads).length === 0) return; + setVisible(true); + setHidden(false); + }, [downloads]); + if (!downloads || Object.keys(downloads).length === 0) return null; + let downloadInProgress = false; if ( - !downloads || - Object.keys(downloads).length === 0 - ) - return null - - - let downloadInProgress = false - if(Object.keys(downloads).find((key)=> (downloads[key]?.status?.status !== 'READY' && downloads[key]?.status?.status !== 'DOWNLOADED'))){ - downloadInProgress = true + Object.keys(downloads).find( + key => + downloads[key]?.status?.status !== "READY" && + downloads[key]?.status?.status !== "DOWNLOADED" + ) + ) { + downloadInProgress = true; } return ( - - - { + handleClick(e); + setOpenDownload(true); + }} + > + {downloadInProgress ? ( + + ) : ( + + )} + + + + - - {Object.keys(downloads) - .map((download: any) => { - const downloadObj = downloads[download] - const progress = downloads[download]?.status?.percentLoaded || 0 - const status = downloads[download]?.status?.status - const service = downloads[download]?.service - return ( - { - const id = downloadObj?.properties?.jsonId - if (!id) return - - navigate( - `/video/${downloadObj?.properties?.user}/${id}` - ) - }} - > - - - {service === 'VIDEO' && ( - - )} - + {Object.keys(downloads).map((download: any) => { + const downloadObj = downloads[download]; + const progress = downloads[download]?.status?.percentLoaded || 0; + const status = downloads[download]?.status?.status; + const service = downloads[download]?.service; + const id = + downloadObj?.identifier + "_metadata-" + downloadObj?.name; + const videoTitle = hashMapVideos[id]?.title; - - - - - {`${progress?.toFixed(0)}%`}{' '} - {status && status === 'REFETCHING' && '- refetching'} - {status && status === 'DOWNLOADED' && '- building'} - - - { + const userName = downloadObj?.name; + const identifier = downloadObj?.identifier; + + if (identifier && userName) + navigate(`/video/${userName}/${identifier}_metadata`); + }} + > + + + {service === "VIDEO" && ( + + )} + + + + - {downloadObj?.identifier} - - - ) - })} - - - + /> + + + {`${progress?.toFixed(0)}%`}{" "} + {status && status === "REFETCHING" && "- refetching"} + {status && status === "DOWNLOADED" && "- building"} + + + + {videoTitle || downloadObj?.identifier} + + + ); + })} + + - - ) -} + ); +}; diff --git a/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx b/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx index 4a7cc53..3afd50c 100644 --- a/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx +++ b/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx @@ -1,16 +1,18 @@ -import { Box, Typography } from "@mui/material"; +import { Box, Tooltip, Typography, useMediaQuery } from "@mui/material"; import React from "react"; +import { PopMenu } from "../PopMenu.tsx"; import ListSuperLikes from "./ListSuperLikes"; import { useSelector } from "react-redux"; import { RootState } from "../../../state/store"; - +import ThumbUpIcon from "@mui/icons-material/ThumbUp"; export const ListSuperLikeContainer = () => { const superlikelist = useSelector( (state: RootState) => state.global.superlikelistAll ); - return ( - + const isScreenLarge = useMediaQuery("(min-width:1200px)"); + const superlikeListComponent = ( + <> { Recent Super likes + + ); + + // @ts-ignore + return ( + + {isScreenLarge ? ( + <>{superlikeListComponent} + ) : ( + + + + + + } + > + {superlikeListComponent} + + )} ); }; diff --git a/src/components/common/Notifications/Notifications.tsx b/src/components/common/Notifications/Notifications.tsx index 4568f8e..654fa3e 100644 --- a/src/components/common/Notifications/Notifications.tsx +++ b/src/components/common/Notifications/Notifications.tsx @@ -64,7 +64,6 @@ export function extractIdValue(metadescription) { } export const Notifications = () => { - const dispatch = useDispatch(); const [anchorElNotification, setAnchorElNotification] = useState(null); const [notifications, setNotifications] = useState([]); @@ -238,7 +237,7 @@ export const Notifications = () => { badgeContent={notificationBadgeLength} color="primary" sx={{ - margin: "0px 12px", + margin: "0px", }} > - + ); }; diff --git a/src/pages/Home/Components/VideoList-styles.tsx b/src/pages/Home/Components/VideoList-styles.tsx index 3a0e48d..eb82aa2 100644 --- a/src/pages/Home/Components/VideoList-styles.tsx +++ b/src/pages/Home/Components/VideoList-styles.tsx @@ -9,6 +9,7 @@ import { Autocomplete, Radio, } from "@mui/material"; +import { fontSizeMedium, fontSizeSmall } from "../../../constants/Misc.ts"; export const VideoContainer = styled(Grid)(({ theme }) => ({ position: "relative", @@ -23,7 +24,7 @@ export const VideoContainer = styled(Grid)(({ theme }) => ({ export const VideoCardContainer = styled("div")(({ theme }) => ({ display: "grid", - gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", + gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: theme.spacing(2), padding: "10px", width: "100%", @@ -31,7 +32,7 @@ export const VideoCardContainer = styled("div")(({ theme }) => ({ export const VideoCardCol = styled("div")({ position: "relative", - minWidth: "250px", // Minimum width of each item + minWidth: "200px", // Minimum width of each item maxWidth: "1fr", // Maximum width, allowing the item to fill the column // ... other styles }); @@ -132,16 +133,14 @@ export const FiltersCol = styled(Grid)(({ theme }) => ({ export const FiltersContainer = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", - justifyContent: "space-between", })); export const FiltersRow = styled(Box)(({ theme }) => ({ display: "flex", alignItems: "center", - justifyContent: "space-between", + justifyContent: "center", width: "100%", - padding: "0 15px", - fontSize: "16px", + fontSize: fontSizeSmall, userSelect: "none", })); @@ -161,7 +160,7 @@ export const FiltersRadioButton = styled(Radio)(({ theme }) => ({ export const FiltersSubContainer = styled(Box)(({ theme }) => ({ display: "flex", - alignItems: "center", + alignItems: "start", flexDirection: "column", gap: "5px", })); diff --git a/src/pages/Home/Components/VideoList.tsx b/src/pages/Home/Components/VideoList.tsx index 725fadd..5e34386 100644 --- a/src/pages/Home/Components/VideoList.tsx +++ b/src/pages/Home/Components/VideoList.tsx @@ -145,7 +145,6 @@ export const VideoList = ({ videos }: VideoListProps) => { maxHeight: "50%", }} /> - {videoObj?.title} { paddingLeft: "0px", paddingRight: "0px", }; + const isScreenSmall = !useMediaQuery("(min-width:600px)"); + const isScreenLarge = useMediaQuery("(min-width:1200px)"); + + const tabSX = { + fontSize: isScreenSmall ? fontSizeSmall : fontSizeLarge, + paddingLeft: "0px", + paddingRight: "0px", + }; + + const homeBaseSX = { display: "grid", width: "100%" }; + const bigGridSX = { gridTemplateColumns: "200px auto 250px" }; + const mediumGridSX = { gridTemplateColumns: "200px auto" }; + const smallGridSX = { gridTemplateColumns: "100%", gap: "20px" }; + + let homeColumns: object; + if (isScreenLarge) homeColumns = bigGridSX; + else if (!isScreenSmall) homeColumns = mediumGridSX; + else homeColumns = smallGridSX; return ( <> - + - - - + + - - - - - - - + + + + + + + + + {filteredSubscriptionList.length > 0 ? ( + <> - - - {filteredSubscriptionList.length > 0 ? ( - <> - - - - ) : !isLoading ? ( -
- You have no subscriptions -
- ) : ( - <> - )} -
-
-
-
-
- - - -
+ + ) : ( + !isLoading && ( +
+ You have no subscriptions +
+ ) + )} + + + + + + ); }; diff --git a/src/state/features/persistSlice.ts b/src/state/features/persistSlice.ts index 6c223ee..c2e2c6f 100644 --- a/src/state/features/persistSlice.ts +++ b/src/state/features/persistSlice.ts @@ -19,6 +19,8 @@ interface settingsState { subscriptionListFilter: SubscriptionListFilterType; showStats: boolean; volume: number; + mutedVolume: number; + isMuted: boolean; } const initialState: settingsState = { @@ -30,6 +32,8 @@ const initialState: settingsState = { subscriptionListFilter: "currentNameOnly", showStats: true, volume: 0.5, + mutedVolume: 0, + isMuted: false, }; export const persistSlice = createSlice({ @@ -77,6 +81,12 @@ export const persistSlice = createSlice({ setVolumeSetting: (state, action: PayloadAction) => { state.volume = action.payload; }, + setMutedVolumeSetting: (state, action: PayloadAction) => { + state.mutedVolume = action.payload; + }, + setIsMuted: (state, action: PayloadAction) => { + state.isMuted = action.payload; + }, }, }); @@ -89,6 +99,8 @@ export const { changeFilterType, resetSubscriptions, setVolumeSetting, + setMutedVolumeSetting, + setIsMuted, } = persistSlice.actions; export default persistSlice.reducer;