mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-12 05:10:52 +02:00
fix vertical height bug
This commit is contained in:
+67
-31
@@ -32,6 +32,8 @@ const SUB_CONTROLS: { icon: IconName; label: string }[] = [
|
||||
|
||||
const DISMISS_DISTANCE = 140;
|
||||
const DISMISS_VELOCITY = 1000;
|
||||
const COMPACT_HEIGHT = 780;
|
||||
const TIGHT_HEIGHT = 700;
|
||||
|
||||
export default function NowPlayingScreen() {
|
||||
const router = useRouter();
|
||||
@@ -44,8 +46,26 @@ export default function NowPlayingScreen() {
|
||||
|
||||
const isPlaying = playbackState === 'playing';
|
||||
const isLoading = playbackState === 'loading';
|
||||
const contentWidth = windowWidth - spacing.xl * 2;
|
||||
const artSize = Math.min(296, contentWidth);
|
||||
const availableHeight = windowHeight - insets.top - insets.bottom;
|
||||
const isCompact = availableHeight < COMPACT_HEIGHT;
|
||||
const isTight = availableHeight < TIGHT_HEIGHT;
|
||||
const contentPadding = isTight ? spacing.lg : spacing.xl;
|
||||
const contentWidth = windowWidth - contentPadding * 2;
|
||||
const artSize = Math.round(
|
||||
Math.min(
|
||||
contentWidth,
|
||||
isTight ? 128 : isCompact ? 200 : 296,
|
||||
Math.max(isTight ? 96 : 144, availableHeight * (isTight ? 0.18 : isCompact ? 0.24 : 0.32))
|
||||
)
|
||||
);
|
||||
const visualizerHeight = isTight ? 58 : isCompact ? 76 : 96;
|
||||
const waveformHeight = isTight ? 42 : isCompact ? 50 : 58;
|
||||
const waveformTouchPadding = isTight ? spacing.xs : spacing.md;
|
||||
const playButtonSize = isTight ? 58 : isCompact ? 62 : 68;
|
||||
const skipIconSize = isTight ? 28 : 32;
|
||||
const playIconSize = isTight ? 30 : 34;
|
||||
const subButtonSize = isTight ? 36 : 40;
|
||||
const subIconSize = isTight ? 18 : 20;
|
||||
const source = track?.album?.trim() ? track.album : 'Library';
|
||||
|
||||
// Swipe down to minimize. The stack transition is disabled for this route, so
|
||||
@@ -107,7 +127,11 @@ export default function NowPlayingScreen() {
|
||||
style={[
|
||||
styles.content,
|
||||
contentStyle,
|
||||
{ paddingTop: insets.top + spacing.sm, paddingBottom: insets.bottom + spacing.lg },
|
||||
{
|
||||
paddingHorizontal: contentPadding,
|
||||
paddingTop: insets.top + (isTight ? spacing.xs : spacing.sm),
|
||||
paddingBottom: insets.bottom + (isTight ? spacing.sm : spacing.lg),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
@@ -129,7 +153,15 @@ export default function NowPlayingScreen() {
|
||||
|
||||
{track ? (
|
||||
<>
|
||||
<View style={styles.artWrap}>
|
||||
<View
|
||||
style={[
|
||||
styles.artWrap,
|
||||
{
|
||||
marginTop: isTight ? spacing.xs : spacing.lg,
|
||||
marginBottom: isTight ? spacing.xs : spacing.lg,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={[styles.art, { width: artSize, height: artSize }]}>
|
||||
{track.artworkData ? (
|
||||
<Image
|
||||
@@ -143,57 +175,75 @@ export default function NowPlayingScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Visualizer width={contentWidth} />
|
||||
<Visualizer width={contentWidth} height={visualizerHeight} />
|
||||
|
||||
<View style={styles.trackInfo}>
|
||||
<View style={[styles.trackInfo, { marginTop: isTight ? spacing.xs : spacing.md }]}>
|
||||
<Text variant="heading" numberOfLines={2} style={styles.centered}>
|
||||
{track.title}
|
||||
</Text>
|
||||
<Text variant="body" numberOfLines={1} style={[styles.centered, styles.artist]}>
|
||||
{track.artist}
|
||||
</Text>
|
||||
<View style={styles.badges}>
|
||||
<View style={[styles.badges, { marginTop: isTight ? spacing.sm : spacing.md }]}>
|
||||
<FormatBadges track={track} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.progressBlock}>
|
||||
<View style={[styles.progressBlock, { marginTop: isTight ? spacing.sm : spacing.lg }]}>
|
||||
<WaveformSeekBar
|
||||
currentTime={currentTime}
|
||||
duration={duration}
|
||||
height={waveformHeight}
|
||||
touchPadding={waveformTouchPadding}
|
||||
trackKey={track.id}
|
||||
trackPath={track.path}
|
||||
onSeek={(seconds) => void seekTo(seconds)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.spacer} />
|
||||
<View style={[styles.spacer, { minHeight: isTight ? spacing.xs : spacing.md }]} />
|
||||
|
||||
<View style={styles.transport}>
|
||||
<View style={[styles.transport, { gap: isTight ? spacing.xl : spacing.xxl }]}>
|
||||
<Pressable onPress={skipToPrevious} hitSlop={12}>
|
||||
<Ionicons name="play-skip-back" size={32} color={colors.textPrimary} />
|
||||
<Ionicons name="play-skip-back" size={skipIconSize} color={colors.textPrimary} />
|
||||
</Pressable>
|
||||
<Pressable onPress={togglePlay} hitSlop={12} style={styles.playButton}>
|
||||
<Pressable
|
||||
onPress={togglePlay}
|
||||
hitSlop={12}
|
||||
style={[styles.playButton, { width: playButtonSize, height: playButtonSize }]}
|
||||
>
|
||||
<Ionicons
|
||||
name={isLoading ? 'ellipsis-horizontal' : isPlaying ? 'pause' : 'play'}
|
||||
size={34}
|
||||
size={playIconSize}
|
||||
color={colors.bgPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable onPress={skipToNext} hitSlop={12}>
|
||||
<Ionicons name="play-skip-forward" size={32} color={colors.textPrimary} />
|
||||
<Ionicons
|
||||
name="play-skip-forward"
|
||||
size={skipIconSize}
|
||||
color={colors.textPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<View style={styles.subRow}>
|
||||
<View
|
||||
style={[
|
||||
styles.subRow,
|
||||
{
|
||||
marginTop: isTight ? spacing.sm : spacing.lg,
|
||||
paddingHorizontal: isTight ? 0 : spacing.sm,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{SUB_CONTROLS.map((c) => (
|
||||
<Pressable
|
||||
key={c.label}
|
||||
hitSlop={10}
|
||||
style={styles.subBtn}
|
||||
style={[styles.subBtn, { width: subButtonSize, height: subButtonSize }]}
|
||||
accessibilityLabel={c.label}
|
||||
>
|
||||
<Ionicons name={c.icon} size={20} color={colors.textTertiary} />
|
||||
<Ionicons name={c.icon} size={subIconSize} color={colors.textTertiary} />
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
@@ -220,7 +270,6 @@ const styles = StyleSheet.create({
|
||||
content: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.bgPrimary,
|
||||
paddingHorizontal: spacing.xl,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
@@ -249,8 +298,6 @@ const styles = StyleSheet.create({
|
||||
artWrap: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: spacing.lg,
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
art: {
|
||||
borderRadius: radius.lg,
|
||||
@@ -264,7 +311,6 @@ const styles = StyleSheet.create({
|
||||
height: '100%',
|
||||
},
|
||||
trackInfo: {
|
||||
marginTop: spacing.md,
|
||||
alignItems: 'center',
|
||||
},
|
||||
centered: {
|
||||
@@ -275,24 +321,18 @@ const styles = StyleSheet.create({
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
badges: {
|
||||
marginTop: spacing.md,
|
||||
},
|
||||
progressBlock: {
|
||||
marginTop: spacing.lg,
|
||||
},
|
||||
spacer: {
|
||||
flex: 1,
|
||||
minHeight: spacing.md,
|
||||
},
|
||||
transport: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: spacing.xxl,
|
||||
},
|
||||
playButton: {
|
||||
width: 68,
|
||||
height: 68,
|
||||
borderRadius: radius.pill,
|
||||
backgroundColor: colors.accent,
|
||||
alignItems: 'center',
|
||||
@@ -302,12 +342,8 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: spacing.lg,
|
||||
paddingHorizontal: spacing.sm,
|
||||
},
|
||||
subBtn: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ type Mode = 'spectrum' | 'scope';
|
||||
* in the layout. Tap anywhere on it to switch between the live filled-line
|
||||
* Spectrum and the Scope (oscilloscope, placeholder until its native path lands).
|
||||
*/
|
||||
export function Visualizer({ width }: { width: number }) {
|
||||
export function Visualizer({ width, height = CANVAS_HEIGHT }: { width: number; height?: number }) {
|
||||
const [mode, setMode] = useState<Mode>('spectrum');
|
||||
const scopeActive = useScopeActive();
|
||||
const spectrumActive = scopeActive && mode === 'spectrum';
|
||||
@@ -39,9 +39,9 @@ export function Visualizer({ width }: { width: number }) {
|
||||
<Ionicons name="swap-horizontal" size={14} color={colors.textTertiary} />
|
||||
</View>
|
||||
|
||||
<View style={{ width, height: CANVAS_HEIGHT }}>
|
||||
<View style={{ width, height }}>
|
||||
{mode === 'spectrum' ? (
|
||||
<SpectrumCurve values={values} width={width} height={CANVAS_HEIGHT} glow />
|
||||
<SpectrumCurve values={values} width={width} height={height} glow />
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Ionicons name="pulse-outline" size={20} color={colors.textTertiary} />
|
||||
|
||||
@@ -18,6 +18,8 @@ interface WaveformSeekBarProps {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
onSeek: (seconds: number) => void;
|
||||
height?: number;
|
||||
touchPadding?: number;
|
||||
/** Identity of the playing track; a pending seek only applies to its own track. */
|
||||
trackKey?: string | number;
|
||||
/** Track file URI used to load/cache the offline waveform peaks. */
|
||||
@@ -36,6 +38,8 @@ export function WaveformSeekBar({
|
||||
currentTime,
|
||||
duration,
|
||||
onSeek,
|
||||
height = CANVAS_HEIGHT,
|
||||
touchPadding = spacing.md,
|
||||
trackKey,
|
||||
trackPath,
|
||||
}: WaveformSeekBarProps) {
|
||||
@@ -125,20 +129,20 @@ export function WaveformSeekBar({
|
||||
const r = BAR_WIDTH / 2;
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
const amp = Math.max(MIN_BAR, display[i] ?? MIN_BAR);
|
||||
const h = amp * CANVAS_HEIGHT;
|
||||
const h = amp * height;
|
||||
const x = i * (BAR_WIDTH + BAR_GAP);
|
||||
const y = (CANVAS_HEIGHT - h) / 2;
|
||||
const y = (height - h) / 2;
|
||||
path.addRRect(Skia.RRectXY(Skia.XYWHRect(x, y, BAR_WIDTH, h), r, r));
|
||||
}
|
||||
return path;
|
||||
}, [source, barCount, barWidth]);
|
||||
}, [source, barCount, barWidth, height]);
|
||||
|
||||
const splitX = fraction * barWidth;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
style={styles.touchArea}
|
||||
style={[styles.touchArea, { height: height + touchPadding * 2 }]}
|
||||
onLayout={onLayout}
|
||||
onStartShouldSetResponder={() => duration > 0}
|
||||
onMoveShouldSetResponder={() => duration > 0}
|
||||
@@ -151,11 +155,11 @@ export function WaveformSeekBar({
|
||||
accessibilityLabel="Seek"
|
||||
accessibilityValue={{ min: 0, max: Math.round(duration), now: Math.round(shownTime) }}
|
||||
>
|
||||
<Canvas style={{ width: '100%', height: CANVAS_HEIGHT }}>
|
||||
<Group clip={rect(0, 0, splitX, CANVAS_HEIGHT)}>
|
||||
<Canvas style={{ width: '100%', height }}>
|
||||
<Group clip={rect(0, 0, splitX, height)}>
|
||||
<Path path={barsPath} color={colors.accent} />
|
||||
</Group>
|
||||
<Group clip={rect(splitX, 0, Math.max(0, barWidth - splitX), CANVAS_HEIGHT)}>
|
||||
<Group clip={rect(splitX, 0, Math.max(0, barWidth - splitX), height)}>
|
||||
<Path path={barsPath} color={colors.glassBorder} />
|
||||
</Group>
|
||||
</Canvas>
|
||||
@@ -175,7 +179,6 @@ export function WaveformSeekBar({
|
||||
const styles = StyleSheet.create({
|
||||
touchArea: {
|
||||
justifyContent: 'center',
|
||||
height: CANVAS_HEIGHT + spacing.md * 2, // generous touch target around the canvas
|
||||
},
|
||||
times: {
|
||||
flexDirection: 'row',
|
||||
|
||||
Reference in New Issue
Block a user