Skip to content

Commit

Permalink
Move song controls, preset and add buttons to bottom of the screen on…
Browse files Browse the repository at this point in the history
… mobile

Add many CSS breakpoints to ensure elements continue to work on the smallest screens

Debounce volume set requests, attempt to sort misinputs out of volume set requests
  • Loading branch information
SteveMicroNova committed Jan 6, 2025
1 parent 5df82be commit 7cc0796
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 74 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
* Webapp
* Update Airplay icon to modern variant
* Add gradient to background to break up the solid color
* Move home screen, player page controls to bottom of screen on mobile
* Update CSS breakpoints to scale the player page better on the smallest of screens
* Reformat Player page volume controls to be more modern
* Add safeguards in an attempt to reduce volume slider misinputs

## 0.4.5
* Web App
Expand Down
13 changes: 7 additions & 6 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,14 @@ Page.propTypes = {

const App = ({ selectedPage }) => {
return (
<div className="app">
<DisconnectedIcon />
<div style={{ paddingBottom: "56px" }}>
<Page selectedPage={selectedPage} />
<div className="app">
<div className="background-gradient"></div> {/* Used to make sure the background doesn't stretch or stop prematurely on scrollable pages */}
<DisconnectedIcon />
<div style={{ paddingBottom: "56px" }}>
<Page selectedPage={selectedPage} />
</div>
<MenuBar pageNumber={selectedPage} />
</div>
<MenuBar pageNumber={selectedPage} />
</div>
);
};
App.propTypes = {
Expand Down
11 changes: 9 additions & 2 deletions web/src/App.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
@use "src/general";

.background-gradient {
background: general.$bg-gradient;
position: fixed;
z-index: -1;
height: 100vh;
width: 100vw;
}

.app {
// display: flex;
// width: 100vw;
height: 100vh; // Required to not have weird paneling with gradient backgrounds
background: general.$bg-gradient;
background: none;
// padding-top: 0.6rem;
// padding-bottom: 0.6rem;
// padding: 0.6rem;
Expand Down
5 changes: 2 additions & 3 deletions web/src/components/CustomMarquee/CustomMarquee.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,13 @@ export default function CustomMarquee(props) {
}
}

let resizeTimout;
let resizeTimeout; // Your IDE will say this is unused, it's actually used to make sure the timeout below is limited to one instance at a time by taking up a specific variable
function handleResize(){
if(!resizeCooldown.current){
resizeCooldown.current = true;

assessMarquee()

resizeTimout = setTimeout(()=>{resizeCooldown.current = false;}, 1000) // set a cooldown for resize checks to avoid excessive renders
resizeTimeout = setTimeout(()=>{resizeCooldown.current = false;}, 1000)
}
}
window.addEventListener("resize", handleResize()); // Doesn't call assessMarquee directly to avoid calling thousands of times per second when resizing window
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,4 @@

.group-volume-slider {
width: 100%;
margin: 0 1rem;
}
6 changes: 3 additions & 3 deletions web/src/components/MediaControl/MediaControl.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
justify-content: center;
margin-top: 1rem;
margin-bottom: 1rem;
width: 90vw;
}

.media-inner {
Expand All @@ -19,6 +18,7 @@
}

.media-control {
font-size: 3.5rem;
color: general.$controls-color;
padding-left: 1.5rem;
padding-right: 1.5rem;
Expand All @@ -30,11 +30,11 @@
padding-right: 1.25rem;
}

@media (min-width: 365px) and (max-width: 425px) {
@media (min-width: 365px) and (max-width: 435px) {
font-size: 2.5rem;
}

@media (min-width: 425px) {
@media (min-width: 435px) {
font-size: 3.5rem;
}
}
Expand Down
5 changes: 5 additions & 0 deletions web/src/components/SongInfo/SongInfo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

white-space: nowrap;
overflow: hidden;

@media (max-width: 435px){
max-height: 50vh;
max-width: 50vh;
}
}

.artist-name {
Expand Down
17 changes: 14 additions & 3 deletions web/src/components/StreamBar/StreamBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,26 @@
color: general.$text-color;
width: 100%;
max-width: 80%; // Leave space for icon
font-size: 2.5rem;
font-weight: medium;
white-space: nowrap;
overflow: hidden;
@include general.header-font;
padding: 0.5rem;
@media (max-height: 500px){
font-size: 1.5rem;
}
@media (min-height: 500px){
font-size: 2.5rem;
}
}

.stream-bar-icon {
width: 4rem;
height: 4rem;
@media (max-height: 500px){
width: 2rem;
height: 2rem;
}
@media (min-height: 500px){
width: 4rem;
height: 4rem;
}
}
71 changes: 48 additions & 23 deletions web/src/components/VolumeSlider/VolumeSlider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ VolIcon.propTypes = {

// generic volume slider used by other volume sliders
const VolumeSlider = ({ vol, mute, setVol, setMute, disabled }) => {
const touchStart = React.useRef(0);
const isScrolling = React.useRef(false);

const handleTouchStart = (e) => {
const touch = e.touches[0];
touchStart.current = touch.clientY;
isScrolling.current = false;
};

const handleTouchMove = (e) => {
const touch = e.touches[0];
const diff = touch.clientY - touchStart.current;
// Detect vertical drag, allow user to continue dragging within safe boundaries without needing to re-drag the slider
isScrolling.current = Math.abs(diff) > 15;
};

let volChangeTimeout;
const handleVolChange = (e, val) => {
volChangeTimeout = setTimeout(() => {
if (!isScrolling.current) {
setVol(val, true);
}
}, 100);
}

return (
<StopProp>
<div className="volume-slider-container">
Expand All @@ -45,29 +70,29 @@ const VolumeSlider = ({ vol, mute, setVol, setMute, disabled }) => {
>
<VolIcon vol={vol} mute={mute} />
</div>
<Slider
disabled={disabled}
className="volume-slider"
min={0}
step={0.01}
max={1}
value={vol}
// https://github.com/mui/material-ui/issues/32737#issuecomment-2060439608
// ios does some weird emulation of mouse events from touch events, ignore those
onChange={(e, val) => {
if(isIOS() && e.type === "mousedown"){
return;
}
setVol(val);
}}
onChangeCommitted={(e, val) => {
if(isIOS() && e.type === "mouseup"){
return;
}
setVol(val, true);
}}
/>
</div>
<Slider
disabled={disabled}
className="volume-slider"
min={0}
step={0.01}
max={1}
value={vol}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onChange={(e, val) => {
if (isIOS() && e.type === "mousedown") {
return;
}
handleVolChange(e, val);
}}
onChangeCommitted={(e, val) => {
if (isIOS() && e.type === "mouseup") {
return;
}
handleVolChange(e, val);
}}
/>
</div>
</StopProp>
);
};
Expand Down
1 change: 1 addition & 0 deletions web/src/components/ZoneVolumeSlider/ZoneVolumeSlider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.zone-volume-container {
@include general.header-font;
font-size: 1rem;
max-width: calc(100% - 47px) // 47px is the width of the dropdown icon + padding, this causes the child volume sliders to end in the same spot as the parent
}
12 changes: 7 additions & 5 deletions web/src/pages/Home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ const PresetAndAdd = ({
);
} else {
return (
<div
className="home-presets-button"
onClick={() => setPresetsModalOpen(true)}
>
Presets
<div className="home-presets-container">
<div
className="home-presets-button"
onClick={() => setPresetsModalOpen(true)}
>
Presets
</div>
</div>
);
}
Expand Down
13 changes: 12 additions & 1 deletion web/src/pages/Home/Home.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

.home-outer {
padding-top: 10px;
padding-bottom: 10px;
@media (max-width: 435px) {
padding-bottom: 85px;
}
@media (min-width: 435px) {
padding-bottom: 10px;
}
display: flex;
flex-direction: column;
align-items: center;
Expand Down Expand Up @@ -69,6 +74,12 @@
}

.home-presets-container {
@media (max-width: 435px) {
position: fixed;
bottom: 65px;
}

z-index: 1; // Needed to ensure that scrolling marquees do not appear on top of the presets button
display: flex;

flex-direction: row;
Expand Down
80 changes: 54 additions & 26 deletions web/src/pages/Player/Player.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,46 @@ const Player = () => {
// This is a bootleg XOR statement, only works if there is exactly one zone or exactly one group, no more than that and not both
const alone = ((usedGroups.length == 1) || (zonesLeft.length == 1)) && !((usedGroups.length > 0) && (zonesLeft.length > 0));

const mobile = window.matchMedia("(max-width: 435px)").matches

selectActiveSource();

function DropdownArrow() {
if(mobile){
if(expanded){
return(
<KeyboardArrowDownIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
)
} else {
return(
<KeyboardArrowUpIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
)
}
} else {
if(expanded){
return(
<KeyboardArrowUpIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
)
} else {
return(
<KeyboardArrowDownIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
)
}
}
}

return (
<div className="player-outer">
{streamsModalOpen && (
Expand All @@ -87,6 +125,7 @@ const Player = () => {
</Grid>
<Grid item xs={2} sm={4} md={4} style={{maxWidth: "22rem"}}>
<Box
className="album-art-container"
sx={{
display: 'flex',
justifyContent: 'center',
Expand All @@ -97,37 +136,26 @@ const Player = () => {
</Box>
<SongInfo sourceId={selectedSourceId} />
</Grid>
<Grid item xs={2} sm={4} md={4}>
<MediaControl selectedSource={selectedSourceId} />
</Grid>
</Grid>
{/* There are many sub-divs classed player-inner here because formatting was strange otherwise */}
<div className="player-inner">
</div>
<div className="player-inner">
</div>
<div className="player-inner">
</div>

<div className="media-controls">
<MediaControl selectedSource={selectedSourceId} />
</div>
{!alone && !is_streamer && zones.length > 0 && (
<Card className="player-volume-slider">
<CardVolumeSlider sourceId={selectedSourceId} />
<IconButton onClick={() => setExpanded(!expanded)}>
{expanded ? (
<KeyboardArrowUpIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
) : (
<KeyboardArrowDownIcon
className="player-volume-expand-button"
style={{ width: "3rem", height: "3rem" }}
/>
)}
</IconButton>
<Card className="player-volume-container">
<div className="player-volume-header">
<CardVolumeSlider sourceId={selectedSourceId} />
<IconButton onClick={() => setExpanded(!expanded)}>
<DropdownArrow />
</IconButton>
</div>

{/* When expanded, take up max height to prevent constant resizes as more dropdowns expand */}
<div className="player-volume-body" style={mobile && expanded ? {height: "100vh"} : {}}>
<VolumeZones open={(expanded || alone)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
</div>
</Card>
)}
<VolumeZones open={(expanded || alone)} sourceId={selectedSourceId} zones={zonesLeft} groups={usedGroups} groupsLeft={groupsLeft} />
</div>
);
};
Expand Down
Loading

0 comments on commit 7cc0796

Please sign in to comment.