diff --git a/mobile/src/navigation/screens/now-playing/sheets/PlaybackSpeedSheet.tsx b/mobile/src/modules/audio/_components/PlaybackSpeedSetting.tsx similarity index 79% rename from mobile/src/navigation/screens/now-playing/sheets/PlaybackSpeedSheet.tsx rename to mobile/src/modules/audio/_components/PlaybackSpeedSetting.tsx index 09f3c45f2..6efac0f8b 100644 --- a/mobile/src/navigation/screens/now-playing/sheets/PlaybackSpeedSheet.tsx +++ b/mobile/src/modules/audio/_components/PlaybackSpeedSetting.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { View } from "react-native"; import AudioBrowser from "react-native-audio-browser"; import type { SharedValue } from "react-native-reanimated"; @@ -9,21 +8,19 @@ import { sessionStore, useSessionStore } from "~/stores/Session/store"; import { Button } from "~/components/Form/Button"; import { CachedSlider } from "~/components/Form/Slider"; -import { DetachedSheet } from "~/components/Sheet"; -import type { TrueSheetRef } from "~/components/Sheet/useSheetRef"; -import { Em } from "~/components/Typography/StyledText"; +import { SegmentedList } from "~/components/List/Segmented"; +import { Em, TStyledText } from "~/components/Typography/StyledText"; -export function PlaybackSpeedSheet(props: { ref: TrueSheetRef }) { - const [stopDrag, setStopDrag] = useState(false); +export function PlaybackSpeedSetting() { const playbackSpeed = useSessionStore((s) => s.playbackSpeed); const cachedPlaybackSpeed = useSharedValue(playbackSpeed); return ( - + + @@ -32,7 +29,7 @@ export function PlaybackSpeedSheet(props: { ref: TrueSheetRef }) { - + ); } @@ -47,7 +44,7 @@ function PlaybackSpeedPreset(props: { setPlaybackSpeed(props.preset); props.value.set(props.preset); }} - className="min-h-8 flex-1 rounded-full py-2 active:bg-surfaceContainer" + className="min-h-8 flex-1 rounded-full bg-surfaceContainerLow py-2 active:bg-surfaceContainer" > {formatValue(props.preset)} @@ -76,10 +73,11 @@ const PlaybackSpeedSliderOptions = { step: 0.05, thickness: 48, onChange: setPlaybackSpeed, + trackColor: "surfaceContainer", overlay: { accessibilityLabelKey: "feat.playback.extra.speed" as const, Icon: SlowMotionVideo, formatValue, }, -}; +} as const; //#endregion diff --git a/mobile/src/modules/audio/_screens.tsx b/mobile/src/modules/audio/_screens.tsx index 271629c11..dd0ff2b22 100644 --- a/mobile/src/modules/audio/_screens.tsx +++ b/mobile/src/modules/audio/_screens.tsx @@ -1,10 +1,22 @@ +import type { StaticScreenProps } from "@react-navigation/native"; + import { ListLayout } from "~/navigation/layouts/ListLayout"; +import { PlaybackSpeedSetting } from "./_components/PlaybackSpeedSetting"; +import { EqualizerSettings } from "./equalizer/components/EqualizerSettings"; import { ReplayGainSettings } from "./replayGain/components/ReplayGainSettings"; -function AudioEffectsView() { +type Props = StaticScreenProps<{ showHidden?: boolean }>; + +function AudioEffectsView({ + route: { + params: { showHidden }, + }, +}: Props) { return ( + + {showHidden ? : null} ); diff --git a/mobile/src/modules/equalizer/components/EQGraph.tsx b/mobile/src/modules/audio/equalizer/components/EQGraph.tsx similarity index 83% rename from mobile/src/modules/equalizer/components/EQGraph.tsx rename to mobile/src/modules/audio/equalizer/components/EQGraph.tsx index 695e6ca14..dd2b2dea8 100644 --- a/mobile/src/modules/equalizer/components/EQGraph.tsx +++ b/mobile/src/modules/audio/equalizer/components/EQGraph.tsx @@ -1,5 +1,5 @@ -import { useMemo } from "react"; -import { View, useWindowDimensions } from "react-native"; +import { createContext, use, useMemo, useState } from "react"; +import { View } from "react-native"; import { Circle, Defs, @@ -12,6 +12,7 @@ import { import { useEqualizerStore } from "../core/store"; import { OnRTL } from "~/lib/react"; +import { cn } from "~/lib/style"; import { Em } from "~/components/Typography/StyledText"; import { useTheme } from "~/modules/customization/theme/hooks"; @@ -21,19 +22,29 @@ const ClampedOrdinate = Ordinate - YPadding; const GraphHeight = Ordinate * 2 + 1; const XAxisYPos = Ordinate + 1; +/** Store graph width in a context as we may render it in other content. */ +const GraphWidthContext = createContext(0); + /** Graph displaying Equalizer configuration based on the Nothing X design. */ export function EQGraph(props: EQLineProps) { + const [graphWidth, setGraphWidth] = useState(0); return ( - - - - - - - + + setGraphWidth(e.nativeEvent.layout.width)} + style={{ height: GraphHeight }} + className={cn( + "relative mb-4 w-full rounded-md bg-surfaceContainerLow", + { "opacity-0": graphWidth === 0 }, + )} + > + + + + + + + ); } @@ -45,7 +56,7 @@ interface EQLineProps { function EQLine(props: EQLineProps) { const { scheme, onSurfaceVariant, surfaceContainerHigh } = useTheme(); - const width = useGraphWidth(); + const width = use(GraphWidthContext); const eqBandOrdinate = useEqualizerStore((s) => s.bandOrdinate); const points = useMemo( @@ -130,7 +141,7 @@ const DisplayedFrequencies = [ /** Draws x-axis and tick marks for certain frequencies. */ function GraphAnnotations() { - const width = useGraphWidth(); + const width = use(GraphWidthContext); const { surfaceContainer } = useTheme(); return ( <> @@ -159,7 +170,7 @@ function GraphAnnotations() { * navigating back when in ``. */ function GraphLabels() { - const width = useGraphWidth(); + const width = use(GraphWidthContext); return DisplayedFrequencies.map(({ label, xPosPercent }) => ( s.defaultFrequencies); + const eqPresets = useEqualizerStore((s) => s.defaultPresets); + const activePreset = useEqualizerStore((s) => s.preset); + + const currEQ = useEqualizerSettings(); + const isEQEnabled = Boolean(currEQ?.enabled); + + const eqDataPoints = useMemo( + () => + eqFreqs.map((freq, index) => ({ + x: freq, + y: currEQ?.bandLevels[index] ?? 0, + })), + [eqFreqs, currEQ?.bandLevels], + ); + + return ( + + } + /> + + + + + + {currEQ?.bandLevels.map((level, index) => ( + + ))} + + + + {eqPresets.map((preset) => { + const isActive = activePreset === preset; + return ( + + ); + })} + + + + + ); +} diff --git a/mobile/src/modules/equalizer/components/FrequencySlider.tsx b/mobile/src/modules/audio/equalizer/components/FrequencySlider.tsx similarity index 95% rename from mobile/src/modules/equalizer/components/FrequencySlider.tsx rename to mobile/src/modules/audio/equalizer/components/FrequencySlider.tsx index 1b0639db7..a85863bd6 100644 --- a/mobile/src/modules/equalizer/components/FrequencySlider.tsx +++ b/mobile/src/modules/audio/equalizer/components/FrequencySlider.tsx @@ -38,13 +38,14 @@ export const FrequencySlider = memo(function FrequencySlider(props: Props) { disabled={props.disabled} hitSlop={10} anchorAt={0} + trackColor="surfaceContainer" roundedEndStop vertical _debounceMultiplier={1} _className="h-48" /> - {bandValue > 0 ? "+" : ""} + {bandValue >= 0 ? "+" : ""} {bandValue / 100} diff --git a/mobile/src/modules/equalizer/core/actions.ts b/mobile/src/modules/audio/equalizer/core/actions.ts similarity index 100% rename from mobile/src/modules/equalizer/core/actions.ts rename to mobile/src/modules/audio/equalizer/core/actions.ts diff --git a/mobile/src/modules/equalizer/core/constants.ts b/mobile/src/modules/audio/equalizer/core/constants.ts similarity index 100% rename from mobile/src/modules/equalizer/core/constants.ts rename to mobile/src/modules/audio/equalizer/core/constants.ts diff --git a/mobile/src/modules/equalizer/core/store.ts b/mobile/src/modules/audio/equalizer/core/store.ts similarity index 100% rename from mobile/src/modules/equalizer/core/store.ts rename to mobile/src/modules/audio/equalizer/core/store.ts diff --git a/mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx b/mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx new file mode 100644 index 000000000..63bbeed17 --- /dev/null +++ b/mobile/src/modules/audio/replayGain/components/PreAmpSlider.tsx @@ -0,0 +1,51 @@ +import { memo } from "react"; +import { View } from "react-native"; + +import { usePlaybackStore } from "~/stores/Playback/store"; +import * as ReplayGain from "../core/actions"; +import { DB_OFFSET } from "../core/constants"; + +import { CachedSlider } from "~/components/Form/Slider"; +import { Em, TEm } from "~/components/Typography/StyledText"; + +export const PreAmpSlider = memo(function PreAmpSlider(props: { + field: "preAmpWTags" | "preAmpWOTags"; + disabled: boolean; +}) { + const preAmpValue = usePlaybackStore((s) => s[props.field]); + return ( + + + + + + + {preAmpValue >= 0 ? "+" : ""} + {preAmpValue.toFixed(1)} dB + + + + ); +}); diff --git a/mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx b/mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx index f03a86a1b..d848a63d8 100644 --- a/mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx +++ b/mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx @@ -1,24 +1,22 @@ import { View } from "react-native"; import { usePlaybackStore } from "~/stores/Playback/store"; -import * as ReplayGain from "../core/actions"; -import { DB_OFFSET } from "../core/constants"; +import { toggleStatus } from "../core/actions"; import { cn } from "~/lib/style"; import { Divider } from "~/components/Divider"; -import { CachedSlider } from "~/components/Form/Slider"; import { SegmentedList } from "~/components/List/Segmented"; -import { Em, TEm, TStyledText } from "~/components/Typography/StyledText"; +import { TStyledText } from "~/components/Typography/StyledText"; import { Switch } from "~/components/UI/Switch"; +import { PreAmpSlider } from "./PreAmpSlider"; export function ReplayGainSettings() { const isReplayGainEnabled = usePlaybackStore((s) => s.isReplayGainEnabled); - return ( } /> @@ -33,56 +31,10 @@ export function ReplayGainSettings() { /> - - + + ); } - -function PreAmpSlider(props: { - variant: "preAmpWTags" | "preAmpWOTags"; - disabled: boolean; -}) { - const preAmpValue = usePlaybackStore((s) => s[props.variant]); - - return ( - - - - - - - {preAmpValue >= 0 ? "+" : ""} - {preAmpValue.toFixed(1)} dB - - - - ); -} diff --git a/mobile/src/modules/equalizer/screens/View.tsx b/mobile/src/modules/equalizer/screens/View.tsx deleted file mode 100644 index 81ccdb366..000000000 --- a/mobile/src/modules/equalizer/screens/View.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { ParseKeys } from "i18next"; -import { useMemo } from "react"; -import { View } from "react-native"; -import { useEqualizerSettings } from "react-native-audio-browser"; - -import { useEqualizerStore } from "../core/store"; -import { toggleEQ, setEQPreset } from "../core/actions"; - -import { ListLayout } from "~/navigation/layouts/ListLayout"; -import { ScreenOptions } from "~/navigation/components/ScreenOptions"; - -import { OnRTL } from "~/lib/react"; -import { cn } from "~/lib/style"; -import { Pressable } from "~/components/Base/Pressable"; -import { Button } from "~/components/Form/Button"; -import { TStyledText } from "~/components/Typography/StyledText"; -import { Switch } from "~/components/UI/Switch"; -import { EQGraph } from "../components/EQGraph"; -import { FrequencySlider } from "../components/FrequencySlider"; - -export default function EqualizerSettings() { - const eqFreqs = useEqualizerStore((s) => s.defaultFrequencies); - const eqPresets = useEqualizerStore((s) => s.defaultPresets); - const activePreset = useEqualizerStore((s) => s.preset); - - const currEQ = useEqualizerSettings(); - - const eqDataPoints = useMemo( - () => - eqFreqs.map((freq, index) => ({ - x: freq, - y: currEQ?.bandLevels[index] ?? 0, - })), - [eqFreqs, currEQ?.bandLevels], - ); - - return ( - <> - ( - - - - )} - /> - - - - - {currEQ?.bandLevels.map((level, index) => ( - - ))} - - - - {eqPresets.map((preset) => { - const isActive = activePreset === preset; - return ( - - ); - })} - - - - ); -} diff --git a/mobile/src/modules/scanning/hooks/useSetup.ts b/mobile/src/modules/scanning/hooks/useSetup.ts index e1c1072a8..09c3f3965 100644 --- a/mobile/src/modules/scanning/hooks/useSetup.ts +++ b/mobile/src/modules/scanning/hooks/useSetup.ts @@ -9,8 +9,11 @@ import { preferenceStore, usePreferenceStore } from "~/stores/Preference/store"; import { equalizerStore, useEqualizerStore, -} from "~/modules/equalizer/core/store"; -import { _initEQStore, setEQPreset } from "~/modules/equalizer/core/actions"; +} from "~/modules/audio/equalizer/core/store"; +import { + _initEQStore, + setEQPreset, +} from "~/modules/audio/equalizer/core/actions"; import { useLyricStore } from "~/modules/lyric/core/store"; import { getAudioBrowserOptions } from "~/lib/react-native-audio-browser"; diff --git a/mobile/src/navigation/components/MiniPlayer.tsx b/mobile/src/navigation/components/MiniPlayer.tsx index 470a4dcde..ac0d65f56 100644 --- a/mobile/src/navigation/components/MiniPlayer.tsx +++ b/mobile/src/navigation/components/MiniPlayer.tsx @@ -136,10 +136,10 @@ export function MiniPlayer() { "bg-surfaceContainerLow": gestureUI && isPressed, })} > - + {track.name} - + {getArtistsString(track.artists)} diff --git a/mobile/src/navigation/routes.tsx b/mobile/src/navigation/routes.tsx index 9809c2eae..3d9bc08d7 100644 --- a/mobile/src/navigation/routes.tsx +++ b/mobile/src/navigation/routes.tsx @@ -50,7 +50,6 @@ import { ArtistsSheet } from "./sheets/ArtistsSheet"; import AudioEffectScreenGroup from "~/modules/audio/_screens"; import FontScreenGroup from "~/modules/customization/font/screens"; import ThemeScreenGroup from "~/modules/customization/theme/screens"; -import EqualizerSettings from "~/modules/equalizer/screens/View"; import LyricScreenGroup from "~/modules/lyric/screens"; import Search from "~/modules/search/screens/View"; @@ -317,10 +316,6 @@ export const RootStack = createNativeStackNavigator({ screen: AppearanceSettings, options: { title: "feat.appearance.title" }, }, - EqualizerSettings: { - screen: EqualizerSettings, - options: { title: "feat.equalizer.title" }, - }, Insights: { screen: Insights, options: { title: "feat.insights.title" }, diff --git a/mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx b/mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx index 031a99353..bd2aff2ca 100644 --- a/mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx +++ b/mobile/src/navigation/screens/now-playing/sheets/PlaybackOptionsSheet.tsx @@ -3,8 +3,7 @@ import { useCallback, useState } from "react"; import AudioBrowser from "react-native-audio-browser"; import { ActivityZone } from "~/resources/icons/ActivityZone"; -import { Equalizer } from "~/resources/icons/Equalizer"; -import { SlowMotionVideo } from "~/resources/icons/SlowMotionVideo"; +import { GraphicEQ } from "~/resources/icons/GraphicEQ"; import { VolumeUp } from "~/resources/icons/VolumeUp"; import { usePlaybackStore } from "~/stores/Playback/store"; import { usePreferenceStore } from "~/stores/Preference/store"; @@ -18,7 +17,6 @@ import { toggleLyricVisibility } from "~/modules/lyric/core/actions"; import { getMediaLinkContext } from "~/navigation/utils/router"; import { AppearanceSheet } from "./AppearanceSheet"; -import { PlaybackSpeedSheet } from "./PlaybackSpeedSheet"; import { Pressable } from "~/components/Base/Pressable"; import { ScrollView } from "~/components/Base/ScrollView"; @@ -46,7 +44,6 @@ export function PlaybackOptionsSheet(props: { const waveformSlider = usePreferenceStore((s) => s.waveformSlider); const volume = useSessionStore((s) => s.volume); const appearanceSheetRef = useSheetRef(); - const playbackSpeedRef = useSheetRef(); const sheetListHandlers = useEnableSheetScroll(true); const navigateToList = useCallback(async () => { @@ -58,9 +55,9 @@ export function PlaybackOptionsSheet(props: { navigation.navigate(...getMediaLinkContext(playingSource)); }, [navigation, props.ref, playingSource]); - const navigateToEQSettings = useCallback(async () => { + const navigateToAudioEffectsScreen = useCallback(async () => { await props.ref.current?.dismiss(); - navigation.navigate("EqualizerSettings"); + navigation.navigate("AudioEffects", { showHidden: true }); }, [navigation, props.ref]); //#region Sheet Presenters @@ -68,17 +65,11 @@ export function PlaybackOptionsSheet(props: { await props.ref.current?.dismiss(); appearanceSheetRef.current?.present(); }, [appearanceSheetRef, props.ref]); - - const presentPlaybackSheet = useCallback(async () => { - await props.ref.current?.dismiss(); - playbackSpeedRef.current?.present(); - }, [playbackSpeedRef, props.ref]); //#endregion return ( <> - } - className="gap-4" - /> - } + labelTextKey="feat.audioEffects.title" + onPress={navigateToAudioEffectsScreen} + LeftElement={} className="gap-4" /> diff --git a/mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx b/mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx index 4b849de61..b80102cb9 100644 --- a/mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx +++ b/mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx @@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next"; import { db } from "~/db"; import { waveformSamples } from "~/db/schema"; -import { Equalizer } from "~/resources/icons/Equalizer"; import { GraphicEQ } from "~/resources/icons/GraphicEQ"; import { OpenInNew } from "~/resources/icons/OpenInNew"; import { Search } from "~/resources/icons/Search"; @@ -61,16 +60,10 @@ export default function ExperimentalSettings() { /> navigation.navigate("AudioEffects")} + onPress={() => navigation.navigate("AudioEffects", {})} LeftElement={} className="gap-4" /> - navigation.navigate("EqualizerSettings")} - LeftElement={} - className="gap-4" - /> - - - ); -}