mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-21 13:09:46 +02:00
improve now playing landscape
This commit is contained in:
@@ -135,6 +135,8 @@ const SKIP_ICON_SIZE = 32;
|
|||||||
const PLAY_ICON_SIZE = 34;
|
const PLAY_ICON_SIZE = 34;
|
||||||
const SUB_BUTTON_SIZE = NOW_PLAYING_SUB_BUTTON_SIZE;
|
const SUB_BUTTON_SIZE = NOW_PLAYING_SUB_BUTTON_SIZE;
|
||||||
const SUB_ICON_SIZE = 20;
|
const SUB_ICON_SIZE = 20;
|
||||||
|
/** Comfortable thumb span for the transport row; see styles.transport. */
|
||||||
|
const TRANSPORT_MAX_WIDTH = 400;
|
||||||
const MENU_ANIMATION_IN_MS = 130;
|
const MENU_ANIMATION_IN_MS = 130;
|
||||||
const MENU_ANIMATION_OUT_MS = 100;
|
const MENU_ANIMATION_OUT_MS = 100;
|
||||||
const MENU_ENTER_OFFSET_Y = -8;
|
const MENU_ENTER_OFFSET_Y = -8;
|
||||||
@@ -1013,7 +1015,11 @@ export function NowPlayingOverlay() {
|
|||||||
style={[
|
style={[
|
||||||
styles.stage,
|
styles.stage,
|
||||||
layout.isWide
|
layout.isWide
|
||||||
? { width: layout.leftPaneWidth }
|
? {
|
||||||
|
width: layout.leftPaneWidth,
|
||||||
|
height: layout.stageHeight,
|
||||||
|
paddingVertical: layout.stageInset,
|
||||||
|
}
|
||||||
: styles.stageFill,
|
: styles.stageFill,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -1046,7 +1052,7 @@ export function NowPlayingOverlay() {
|
|||||||
styles.deckSpread,
|
styles.deckSpread,
|
||||||
{ rowGap: deck.rowGap },
|
{ rowGap: deck.rowGap },
|
||||||
layout.isWide
|
layout.isWide
|
||||||
? { width: layout.rightPaneWidth }
|
? { width: layout.rightPaneWidth, height: deck.height }
|
||||||
: { height: deck.height },
|
: { height: deck.height },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -1291,7 +1297,11 @@ export function NowPlayingOverlay() {
|
|||||||
style={[
|
style={[
|
||||||
styles.stage,
|
styles.stage,
|
||||||
layout.isWide
|
layout.isWide
|
||||||
? { width: layout.leftPaneWidth }
|
? {
|
||||||
|
width: layout.leftPaneWidth,
|
||||||
|
height: layout.stageHeight,
|
||||||
|
paddingVertical: layout.stageInset,
|
||||||
|
}
|
||||||
: styles.stageFill,
|
: styles.stageFill,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -1375,7 +1385,7 @@ export function NowPlayingOverlay() {
|
|||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
{railStyle && layout.isWide && renderScopeSurfaces && (
|
{railStyle && layout.isWide && layout.scopeRailFits && renderScopeSurfaces && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.scopeRail,
|
styles.scopeRail,
|
||||||
@@ -1404,7 +1414,7 @@ export function NowPlayingOverlay() {
|
|||||||
styles.deck,
|
styles.deck,
|
||||||
{ rowGap: deck.rowGap },
|
{ rowGap: deck.rowGap },
|
||||||
layout.isWide
|
layout.isWide
|
||||||
? { width: layout.rightPaneWidth }
|
? { width: layout.rightPaneWidth, height: deck.height }
|
||||||
: { height: deck.height },
|
: { height: deck.height },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -2175,6 +2185,12 @@ const useStyles = createThemedStyles((colors) => ({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
// The seek bar above wants every pixel of a wide landscape deck; the
|
||||||
|
// transport does not — past this the buttons just drift to the far corners.
|
||||||
|
// Centred under a full-width bar, which is how wide players lay this out.
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: TRANSPORT_MAX_WIDTH,
|
||||||
|
alignSelf: 'center',
|
||||||
},
|
},
|
||||||
transportMainBtn: {
|
transportMainBtn: {
|
||||||
width: 48,
|
width: 48,
|
||||||
|
|||||||
@@ -319,6 +319,122 @@ test('caps reserved line boxes so a huge font setting cannot run away', () => {
|
|||||||
assert.ok(capped.deck.height > layoutFor(device, 1, true).deck.height);
|
assert.ok(capped.deck.height > layoutFor(device, 1, true).deck.height);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LANDSCAPE = [
|
||||||
|
{ name: 'Pixel 7 Pro landscape', width: 891, height: 339 },
|
||||||
|
{ name: 'S22 landscape', width: 780, height: 312 },
|
||||||
|
{ name: 'Poco M5 landscape', width: 873, height: 345 },
|
||||||
|
{ name: 'S25 Ultra landscape', width: 918, height: 363 },
|
||||||
|
{ name: 'Tablet 12" landscape', width: 1366, height: 1000 },
|
||||||
|
{ name: 'Foldable open landscape', width: 800, height: 650 },
|
||||||
|
{ name: 'very short landscape', width: 800, height: 300 },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
test('landscape sizes its panes from their contents, not a fixed split', () => {
|
||||||
|
for (const fontScale of FONT_SCALES) {
|
||||||
|
for (const window of LANDSCAPE) {
|
||||||
|
const layout = getNowPlayingLayout(
|
||||||
|
window.width,
|
||||||
|
window.height,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
fontScale
|
||||||
|
);
|
||||||
|
assert.equal(layout.presentation, 'wide', `${window.name} should be wide`);
|
||||||
|
assert.equal(
|
||||||
|
layout.leftPaneWidth + 32 + layout.rightPaneWidth,
|
||||||
|
layout.contentWidth,
|
||||||
|
`${window.name}: panes + gap must equal the row width`
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
layout.contentWidth <= window.width - layout.contentPadding * 2,
|
||||||
|
`${window.name}: row ${layout.contentWidth} overflows the window`
|
||||||
|
);
|
||||||
|
// The stage pane exists to hold the artwork and its strip; a proportional
|
||||||
|
// split used to hand a 160dp artwork a 432dp pane.
|
||||||
|
assert.equal(
|
||||||
|
layout.leftPaneWidth,
|
||||||
|
Math.max(layout.artSizeScopeOff, layout.scopeWidth),
|
||||||
|
`${window.name}: stage pane should hug its widest content`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('landscape actually uses the width it is given', () => {
|
||||||
|
// The deck cap was aliased to the portrait column width, leaving 100-150dp of
|
||||||
|
// a phone's landscape row unused. Height is scarce in landscape and the
|
||||||
|
// artwork is square, so the deck is the only pane that can spend the surplus.
|
||||||
|
for (const window of LANDSCAPE) {
|
||||||
|
if (window.width > 1000) continue; // tablets are capped by design, for now
|
||||||
|
const layout = getNowPlayingLayout(window.width, window.height, true, false, 1);
|
||||||
|
const rowSpace = Math.min(window.width - layout.contentPadding * 2, 960);
|
||||||
|
const unused = rowSpace - layout.contentWidth;
|
||||||
|
assert.ok(
|
||||||
|
unused <= 40,
|
||||||
|
`${window.name}: ${unused}dp of the row goes unused (row ${layout.contentWidth} of ${rowSpace})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('landscape keeps the scope strip in proportion to the artwork', () => {
|
||||||
|
for (const window of LANDSCAPE) {
|
||||||
|
const layout = getNowPlayingLayout(window.width, window.height, true, false, 1);
|
||||||
|
if (!layout.scopeRailFits) continue;
|
||||||
|
assert.ok(
|
||||||
|
layout.scopeWidth >= layout.artSizeScopeOn,
|
||||||
|
`${window.name}: strip ${layout.scopeWidth} narrower than artwork ${layout.artSizeScopeOn}`
|
||||||
|
);
|
||||||
|
// Was 2.7x on a Pixel in landscape, which read as a box beside the art.
|
||||||
|
assert.ok(
|
||||||
|
layout.scopeWidth <= layout.artSizeScopeOn * 1.6 + 1,
|
||||||
|
`${window.name}: strip ${layout.scopeWidth} is out of proportion to artwork ${layout.artSizeScopeOn}`
|
||||||
|
);
|
||||||
|
assert.equal(layout.scopeHeight, getScopeHeight(layout.scopeWidth));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('landscape picks the richest deck the column can hold', () => {
|
||||||
|
const rank = { compact: 0, regular: 1, spacious: 2 } as const;
|
||||||
|
for (const fontScale of FONT_SCALES) {
|
||||||
|
for (const window of LANDSCAPE) {
|
||||||
|
const layout = getNowPlayingLayout(
|
||||||
|
window.width,
|
||||||
|
window.height,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
fontScale
|
||||||
|
);
|
||||||
|
const column =
|
||||||
|
window.height -
|
||||||
|
NOW_PLAYING_CONTENT_TOP_PADDING -
|
||||||
|
NOW_PLAYING_CONTENT_BOTTOM_PADDING -
|
||||||
|
NOW_PLAYING_HEADER_HEIGHT;
|
||||||
|
// Either the deck fits, or it is the leanest tier and the window is the
|
||||||
|
// one at fault.
|
||||||
|
assert.ok(
|
||||||
|
layout.deck.height <= column || layout.density === 'compact',
|
||||||
|
`${window.name} @${fontScale}: '${layout.density}' deck ${layout.deck.height} exceeds column ${column}`
|
||||||
|
);
|
||||||
|
assert.equal(layout.stageHeight, column);
|
||||||
|
assert.ok(rank[layout.density] >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('landscape never grows the artwork when the scope comes on', () => {
|
||||||
|
for (const fontScale of FONT_SCALES) {
|
||||||
|
for (const window of LANDSCAPE) {
|
||||||
|
const hidden = getNowPlayingLayout(window.width, window.height, false, false, fontScale);
|
||||||
|
const visible = getNowPlayingLayout(window.width, window.height, true, false, fontScale);
|
||||||
|
assert.equal(hidden.deck.height, visible.deck.height);
|
||||||
|
assert.equal(hidden.leftPaneWidth, visible.leftPaneWidth);
|
||||||
|
assert.equal(hidden.rightPaneWidth, visible.rightPaneWidth);
|
||||||
|
assert.equal(hidden.contentWidth, visible.contentWidth);
|
||||||
|
assert.ok(visible.artSizeScopeOn <= visible.artSizeScopeOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('adds the companion only to roomy tablet canvases', () => {
|
test('adds the companion only to roomy tablet canvases', () => {
|
||||||
for (const device of DEVICES) {
|
for (const device of DEVICES) {
|
||||||
assert.equal(getTabletCompanionLayout(device.width, device.height, true), null);
|
assert.equal(getTabletCompanionLayout(device.width, device.height, true), null);
|
||||||
|
|||||||
@@ -25,10 +25,24 @@ const TABLET_ART_SIZE_MAX = 440;
|
|||||||
const WIDE_MAX_CONTENT_WIDTH = 960;
|
const WIDE_MAX_CONTENT_WIDTH = 960;
|
||||||
export const NOW_PLAYING_WIDE_PANE_GAP = spacing.xxl;
|
export const NOW_PLAYING_WIDE_PANE_GAP = spacing.xxl;
|
||||||
const WIDE_RIGHT_PANE_MIN = 300;
|
const WIDE_RIGHT_PANE_MIN = 300;
|
||||||
const WIDE_RIGHT_PANE_MAX = MAX_CONTENT_WIDTH;
|
/**
|
||||||
|
* Landscape deck pane cap. Height is the scarce resource in landscape and the
|
||||||
|
* artwork is square, so it can never spend the surplus width — the deck is the
|
||||||
|
* only pane that can, and a longer seek bar is the point. Was aliased to the
|
||||||
|
* portrait column width (408), which left 100-150dp of a phone's landscape row
|
||||||
|
* simply unused.
|
||||||
|
*/
|
||||||
|
const WIDE_RIGHT_PANE_MAX = 560;
|
||||||
const WIDE_ART_SIZE_MAX = 400;
|
const WIDE_ART_SIZE_MAX = 400;
|
||||||
const WIDE_ART_SIZE_MIN = 160;
|
const WIDE_ART_SIZE_MIN = 160;
|
||||||
const WIDE_COMPACT_HEIGHT = 480;
|
/**
|
||||||
|
* How much wider than the artwork the scope strip runs. Mirrors the portrait
|
||||||
|
* proportion (~1.5x), where the strip reads as a rail under the art rather than
|
||||||
|
* a box beside it.
|
||||||
|
*/
|
||||||
|
const WIDE_SCOPE_WIDTH_RATIO = 1.5;
|
||||||
|
/** Below this the landscape artwork is too small to give the strip its share. */
|
||||||
|
const WIDE_SCOPE_RAIL_MIN_ART = 120;
|
||||||
const VISUALIZER_WIDTH_MAX = 448;
|
const VISUALIZER_WIDTH_MAX = 448;
|
||||||
const VISUALIZER_SIDE_PADDING = spacing.md;
|
const VISUALIZER_SIDE_PADDING = spacing.md;
|
||||||
const VISUALIZER_TOP_GAP = spacing.lg;
|
const VISUALIZER_TOP_GAP = spacing.lg;
|
||||||
@@ -318,38 +332,104 @@ export function getNowPlayingLayout(
|
|||||||
): NowPlayingLayout {
|
): NowPlayingLayout {
|
||||||
const isWide = forceWide || isWideWindow(availableWidth, availableHeight);
|
const isWide = forceWide || isWideWindow(availableWidth, availableHeight);
|
||||||
|
|
||||||
if (isWide) {
|
const columnHeight =
|
||||||
const contentPadding = CONTENT_SIDE_PADDING;
|
|
||||||
const contentWidth = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(availableWidth - contentPadding * 2, WIDE_MAX_CONTENT_WIDTH)
|
|
||||||
);
|
|
||||||
const rightPaneWidth = Math.round(
|
|
||||||
clamp(contentWidth * 0.46, WIDE_RIGHT_PANE_MIN, WIDE_RIGHT_PANE_MAX)
|
|
||||||
);
|
|
||||||
const leftPaneWidth = Math.max(
|
|
||||||
0,
|
|
||||||
contentWidth - NOW_PLAYING_WIDE_PANE_GAP - rightPaneWidth
|
|
||||||
);
|
|
||||||
const scopeWidth = Math.min(leftPaneWidth, VISUALIZER_WIDTH_MAX);
|
|
||||||
const scopeHeight = getScopeHeight(scopeWidth);
|
|
||||||
const visualizerTopGap = showVisualizer ? VISUALIZER_TOP_GAP : 0;
|
|
||||||
const verticalBudget =
|
|
||||||
availableHeight -
|
availableHeight -
|
||||||
NOW_PLAYING_CONTENT_TOP_PADDING -
|
NOW_PLAYING_CONTENT_TOP_PADDING -
|
||||||
NOW_PLAYING_CONTENT_BOTTOM_PADDING -
|
NOW_PLAYING_CONTENT_BOTTOM_PADDING -
|
||||||
NOW_PLAYING_HEADER_HEIGHT -
|
NOW_PLAYING_HEADER_HEIGHT;
|
||||||
spacing.md;
|
|
||||||
const wideArt = (budget: number) =>
|
if (isWide) {
|
||||||
Math.round(clamp(Math.min(leftPaneWidth, budget), WIDE_ART_SIZE_MIN, WIDE_ART_SIZE_MAX));
|
const contentPadding = CONTENT_SIDE_PADDING;
|
||||||
const artSizeScopeOn = wideArt(verticalBudget - (scopeHeight + VISUALIZER_TOP_GAP));
|
const rowSpace = Math.max(
|
||||||
const artSizeScopeOff = wideArt(verticalBudget);
|
0,
|
||||||
const artSize = showVisualizer ? artSizeScopeOn : artSizeScopeOff;
|
Math.min(availableWidth - contentPadding * 2, WIDE_MAX_CONTENT_WIDTH)
|
||||||
// Short landscape windows get the leaner deck tokens.
|
|
||||||
const deck = getDeckHeight(
|
|
||||||
availableHeight < WIDE_COMPACT_HEIGHT ? TIERS[2] : TIERS[1],
|
|
||||||
fontScale
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solve one tier's landscape geometry.
|
||||||
|
*
|
||||||
|
* Panes sit side by side, so unlike portrait the deck costs the artwork no
|
||||||
|
* height — the constraint is that the deck fits the column at all. The
|
||||||
|
* artwork is height-bound in almost every landscape window, which is why
|
||||||
|
* the panes are sized from their *contents* here and the pair is centred:
|
||||||
|
* a proportional split gave a 160dp artwork a 432dp pane to rattle around
|
||||||
|
* in, with a scope strip nearly three times its width beneath it.
|
||||||
|
*/
|
||||||
|
const solve = (tier: DensityTier) => {
|
||||||
|
const deck = getDeckHeight(tier, fontScale);
|
||||||
|
// Budget against the *narrowest* the deck may be, so the artwork gets
|
||||||
|
// first call on the row's width; the deck reclaims whatever is left over
|
||||||
|
// once the stage has been sized (a longer waveform is worth having in
|
||||||
|
// landscape, and it is the only thing here that can use loose width).
|
||||||
|
const stageSpace = Math.max(
|
||||||
|
0,
|
||||||
|
rowSpace - NOW_PLAYING_WIDE_PANE_GAP - WIDE_RIGHT_PANE_MIN
|
||||||
|
);
|
||||||
|
const inner = Math.max(0, columnHeight - tier.stageInset * 2);
|
||||||
|
const fitArt = (space: number) =>
|
||||||
|
Math.round(Math.max(0, Math.min(space, stageSpace, WIDE_ART_SIZE_MAX)));
|
||||||
|
|
||||||
|
// The strip's height follows its width, which follows the artwork, which
|
||||||
|
// depends on the strip's height. Seed with the tallest strip it could be
|
||||||
|
// and refine once — `getScopeHeight` is clamped to a 12dp band, so a
|
||||||
|
// second pass is enough to land on a stable answer.
|
||||||
|
let scopeHeight = VISUALIZER_HEIGHT_MAX;
|
||||||
|
let scopeWidth = 0;
|
||||||
|
let artScopeOn = 0;
|
||||||
|
for (let pass = 0; pass < 2; pass += 1) {
|
||||||
|
artScopeOn = fitArt(
|
||||||
|
inner - (tier.scopeTopGap + scopeHeight + tier.scopeBottomGap)
|
||||||
|
);
|
||||||
|
scopeWidth = Math.round(
|
||||||
|
clamp(
|
||||||
|
artScopeOn * WIDE_SCOPE_WIDTH_RATIO,
|
||||||
|
artScopeOn,
|
||||||
|
Math.min(stageSpace, VISUALIZER_WIDTH_MAX)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
scopeHeight = getScopeHeight(scopeWidth);
|
||||||
|
}
|
||||||
|
const artScopeOff = fitArt(inner);
|
||||||
|
const naturalScopeBlock = tier.scopeTopGap + scopeHeight + tier.scopeBottomGap;
|
||||||
|
const scopeRailFits = artScopeOn >= WIDE_SCOPE_RAIL_MIN_ART;
|
||||||
|
return {
|
||||||
|
tier,
|
||||||
|
deck,
|
||||||
|
scopeWidth,
|
||||||
|
scopeHeight,
|
||||||
|
artScopeOn: scopeRailFits ? artScopeOn : artScopeOff,
|
||||||
|
artScopeOff,
|
||||||
|
scopeBlockHeight: scopeRailFits ? naturalScopeBlock : 0,
|
||||||
|
scopeRailFits,
|
||||||
|
fits: deck.height <= columnHeight && artScopeOff >= WIDE_ART_SIZE_MIN,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Richest tier whose deck fits the column outright. In landscape the deck
|
||||||
|
// is the thing that runs out of room first, so this replaces the old raw
|
||||||
|
// `availableHeight < 480` threshold with the same ladder portrait uses.
|
||||||
|
let solved = solve(TIERS[TIERS.length - 1]);
|
||||||
|
for (const candidate of TIERS) {
|
||||||
|
const attempt = solve(candidate);
|
||||||
|
if (attempt.fits) {
|
||||||
|
solved = attempt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tier, deck } = solved;
|
||||||
|
const artSize = showVisualizer ? solved.artScopeOn : solved.artScopeOff;
|
||||||
|
// Panes are content-sized and the row is centred by the shell being exactly
|
||||||
|
// this wide. The deck takes the width the stage didn't need.
|
||||||
|
const leftPaneWidth = Math.max(solved.artScopeOff, solved.scopeWidth);
|
||||||
|
const rightPaneWidth = Math.round(
|
||||||
|
clamp(
|
||||||
|
rowSpace - NOW_PLAYING_WIDE_PANE_GAP - leftPaneWidth,
|
||||||
|
WIDE_RIGHT_PANE_MIN,
|
||||||
|
WIDE_RIGHT_PANE_MAX
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const contentWidth = leftPaneWidth + NOW_PLAYING_WIDE_PANE_GAP + rightPaneWidth;
|
||||||
return {
|
return {
|
||||||
presentation: 'wide',
|
presentation: 'wide',
|
||||||
isWide: true,
|
isWide: true,
|
||||||
@@ -359,18 +439,18 @@ export function getNowPlayingLayout(
|
|||||||
leftPaneWidth,
|
leftPaneWidth,
|
||||||
rightPaneWidth,
|
rightPaneWidth,
|
||||||
deck,
|
deck,
|
||||||
stageHeight: showVisualizer ? artSize + visualizerTopGap + scopeHeight : artSize,
|
stageHeight: columnHeight,
|
||||||
stageInset: 0,
|
stageInset: tier.stageInset,
|
||||||
scopeBlockHeight: showVisualizer ? visualizerTopGap + scopeHeight : 0,
|
scopeBlockHeight: solved.scopeBlockHeight,
|
||||||
railBottomOffset: 0,
|
railBottomOffset: 0,
|
||||||
scopeRailFits: true,
|
scopeRailFits: solved.scopeRailFits,
|
||||||
artSize,
|
artSize,
|
||||||
artSizeScopeOn,
|
artSizeScopeOn: solved.artScopeOn,
|
||||||
artSizeScopeOff,
|
artSizeScopeOff: solved.artScopeOff,
|
||||||
scopeWidth,
|
scopeWidth: solved.scopeWidth,
|
||||||
scopeHeight,
|
scopeHeight: solved.scopeHeight,
|
||||||
visualizerTopGap,
|
visualizerTopGap: tier.scopeTopGap,
|
||||||
visualizerBottomGap: 0,
|
visualizerBottomGap: tier.scopeBottomGap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,11 +466,6 @@ export function getNowPlayingLayout(
|
|||||||
Math.min(availableWidth - VISUALIZER_SIDE_PADDING * 2, VISUALIZER_WIDTH_MAX)
|
Math.min(availableWidth - VISUALIZER_SIDE_PADDING * 2, VISUALIZER_WIDTH_MAX)
|
||||||
);
|
);
|
||||||
const scopeHeight = getScopeHeight(scopeWidth);
|
const scopeHeight = getScopeHeight(scopeWidth);
|
||||||
const columnHeight =
|
|
||||||
availableHeight -
|
|
||||||
NOW_PLAYING_CONTENT_TOP_PADDING -
|
|
||||||
NOW_PLAYING_CONTENT_BOTTOM_PADDING -
|
|
||||||
NOW_PLAYING_HEADER_HEIGHT;
|
|
||||||
|
|
||||||
const artWidthCap = (tier: DensityTier) =>
|
const artWidthCap = (tier: DensityTier) =>
|
||||||
Math.min(contentWidth, isTabletColumn ? TABLET_ART_SIZE_MAX : tier.artMax);
|
Math.min(contentWidth, isTabletColumn ? TABLET_ART_SIZE_MAX : tier.artMax);
|
||||||
|
|||||||
Reference in New Issue
Block a user