Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-handed mobile control #1011

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* Update Airplay icon to modern variant
* Add gradient to background to break up the solid color
* Reformat Admin Settings portion of Admin Panel (previously known as the Updater)
* 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 look more modern
* Add safeguards in an attempt to reduce volume slider misinputs

## 0.4.5
* Web App
Expand Down
11 changes: 6 additions & 5 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">
<div className="app">
<DisconnectedIcon />
<div className="app-body">
<Page selectedPage={selectedPage} />
<div className="background-gradient"></div> {/* Used to make sure the background doesn't stretch or stop prematurely on scrollable pages */}
<div className="app-body">
<Page selectedPage={selectedPage} />
</div>
<MenuBar pageNumber={selectedPage} />
</div>
<MenuBar pageNumber={selectedPage} />
</div>
);
};
App.propTypes = {
Expand Down
40 changes: 38 additions & 2 deletions web/src/App.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
@use "src/general";

.background-gradient {
background: general.$bg-gradient;
position: fixed;
z-index: -1;
height: 100vh;
width: 100vw;
}
Comment on lines +3 to +9
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to remake how the gradient background worked, if you have enough zones when you use the dropdown on the player page you leave the gradient behind as you scroll at 100vh, and at 100% the gradient stretches weird

Now it stays the same no matter where you scroll


.app {
// display: flex;
// width: 100vw;
height: 100vh; // Required to not have weird paneling with gradient backgrounds
background: general.$bg-gradient;
// padding-top: 0.6rem;
// padding-bottom: 0.6rem;
// padding: 0.6rem;
Expand All @@ -24,3 +30,33 @@
.app-body {
padding-bottom: general.$navbar-height;
}


.pill-scrollbar {
max-height: inherit;
overflow-y: auto;
}

.pill-scrollbar::-webkit-scrollbar {
width: 12px;
height: 12px;
}

.pill-scrollbar::-webkit-scrollbar-track {
background: transparent;
}

.pill-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.5);
border-radius: 999px;
border: 3px solid rgba(255, 255, 255, 0.5);
}

.pill-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.7);
}

.pill-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.5) transparent;
}
10 changes: 8 additions & 2 deletions web/src/components/Card/Card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ const Card = ({
selected,
selectable,
onClick,

secondary,
}) => {
const cName = `card ${className} ${selectable && !selected ? "selectable" : ""} ${selected ? "selected" : ""}`
const cName = `card ${className} ${selectable && !selected ? "selectable" : ""} ${selected ? "selected" : ""} ${secondary ? "secondary" : "primary"}`
const onClickFun = onClick === null ? () => {} : onClick;
const topTransparency = selected ? 0.25 : 0.4;
const bottomTransparency = selected ? 0.25 : 0.9;
Expand Down Expand Up @@ -49,14 +51,18 @@ Card.propTypes = {
selected: PropTypes.bool,
selectable: PropTypes.bool,
onClick: PropTypes.func,

secondary: PropTypes.bool,
};

Card.defaultProps = {
backgroundImage: null,
className: "",
selected: false,
selectable: false,
onClick: null
onClick: null,

secondary: false,
};

export default Card;
8 changes: 7 additions & 1 deletion web/src/components/Card/Card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

.card {
@include general.low-shadow;
background-color: general.$primary;
border-radius: 18px;

color: general.$text-color;
}

.primary {
background-color: general.$primary
}
.secondary {
background-color: general.$secondary
}

.selected {
@include general.selected-shadow;
}
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) {
Comment on lines -33 to +37
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a specific type of apple phone that needed to fit in this bucket, so I expanded the definition of this everywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ought to make this an scss var to avoid such wide-touching bucketing changes, but I'm uncertain I want to bloat this PR any more
If you want that change to make it into this one, say so; otherwise I will make another discreet PR shortly after this is merged.

font-size: 3.5rem;
}
}
Expand Down
30 changes: 0 additions & 30 deletions web/src/components/ModalCard/ModalCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,3 @@
.modal-footer-button {
@include general.button-hover;
}


.pill-scrollbar {
max-height: inherit;
overflow-y: auto;
}

.pill-scrollbar::-webkit-scrollbar {
width: 12px;
height: 12px;
}

.pill-scrollbar::-webkit-scrollbar-track {
background: transparent;
}

.pill-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.5);
border-radius: 999px;
border: 3px solid rgba(255, 255, 255, 0.5);
}

.pill-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.7);
}

.pill-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.5) transparent;
}
6 changes: 6 additions & 0 deletions web/src/components/SongInfo/SongInfo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

white-space: nowrap;
overflow: hidden;

// Used to ensure the smallest screens still have room for all metadata and controls on one screen without scrolling
@media (max-width: 435px){
max-height: 50vh;
max-width: 50vh;
}
}

.artist-name {
Expand Down
18 changes: 15 additions & 3 deletions web/src/components/StreamBar/StreamBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
max-width: 22rem; // The same width as the album art, until the album art starts to shrink at 375px wide
}
@media (max-width: 375px) {
max-height: 7.5vh;
max-width: 85vw;
}
}
Expand All @@ -21,15 +22,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;
}
}
74 changes: 51 additions & 23 deletions web/src/components/VolumeSlider/VolumeSlider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ VolIcon.propTypes = {

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

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

const handleTouchMove = (e) => {
const touch = e.touches[0];
const diffX = touch.clientX - touchStartX.current;
const diffY = touch.clientY - touchStartY.current;

// Detect vertical drag, allow user to continue dragging within safe boundaries without needing to re-drag the slider
isScrolling.current = (Math.abs(diffY) / Math.abs(diffX)) > 1.65; // Equivalent to approximately 60 deg
};

const handleVolChange = (val, force = false) => {
if (!isScrolling.current) {
setVol(val, force);
}
}

return (
<StopProp>
<div className="volume-slider-container">
Expand All @@ -45,29 +71,31 @@ 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}
// 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;
}
handleVolChange(val);
}}
onChangeCommitted={(e, val) => {
if (isIOS() && e.type === "mouseup") {
return;
}
handleVolChange(val, true);
}}
/>
</div>
</StopProp>
);
};
Expand Down
13 changes: 9 additions & 4 deletions web/src/components/VolumeZones/VolumeZones.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import Card from "../Card/Card";

import PropTypes from "prop-types";

const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft }) => {
const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft, alone }) => {
const groupVolumeSliders = [];
for (const group of groups) {
groupVolumeSliders.push(
<Card className="group-vol-card" key={group.id}>
<Card secondary={!alone} className={`group-vol-card ${!alone ? "vol-margin" : ""}`} key={group.id}>
<GroupVolumeSlider
alone={alone}
groupId={group.id}
sourceId={sourceId}
groupsLeft={groupsLeft}
Expand All @@ -23,8 +24,8 @@ const VolumeZones = ({ sourceId, open, zones, groups, groupsLeft }) => {
const zoneVolumeSliders = [];
zones.forEach((zone) => {
zoneVolumeSliders.push(
<Card className="zone-vol-card" key={zone.id}>
<ZoneVolumeSlider zoneId={zone.id} />
<Card secondary={!alone} className={`zone-vol-card ${!alone ? "vol-margin" : ""}`} key={zone.id}>
<ZoneVolumeSlider alone={alone} zoneId={zone.id} />
</Card>
);
});
Expand All @@ -44,6 +45,10 @@ VolumeZones.propTypes = {
zones: PropTypes.array.isRequired,
groups: PropTypes.array.isRequired,
groupsLeft: PropTypes.array.isRequired,
alone: PropTypes.bool,
};
VolumeZones.defaultProps = {
alone: false,
}

export default VolumeZones;
Loading
Loading