From ed5811017113872fec3538139997f9fbc588ca45 Mon Sep 17 00:00:00 2001
From: Boof2015 <75185879+Boof2015@users.noreply.github.com>
Date: Tue, 11 Aug 2026 17:42:57 -0400
Subject: [PATCH] remove ripple
---
eslint.config.js | 9 ++
.../queue/QueueContentView.kt | 20 ++--
.../astralibraryscanner/queue/QueuePalette.kt | 4 +-
modules/astra-library-scanner/queue.ts | 4 +-
package.json | 1 +
scripts/run-release-tests.mjs | 1 +
src/app/(tabs)/eq.tsx | 20 ++--
src/app/(tabs)/index.tsx | 58 +++++------
src/app/(tabs)/library/artist/[name].tsx | 13 +--
src/app/(tabs)/library/index.tsx | 22 ++---
src/app/(tabs)/library/playlist/[id].tsx | 15 ++-
.../(tabs)/library/playlist/edit-dynamic.tsx | 55 +++++------
src/app/(tabs)/stats.tsx | 14 ++-
src/app/desktop-remote.tsx | 49 +++++-----
src/app/desktop-remote/scan.tsx | 12 +--
src/app/desktop-sync.tsx | 25 +++--
src/app/eq/import.tsx | 13 ++-
src/app/eq/scan.tsx | 16 ++--
src/app/lastfm/edit.tsx | 20 ++--
src/app/lastfm/index.tsx | 24 +++--
src/app/settings/haptics-lab.tsx | 18 ++--
src/app/settings/playback.tsx | 11 +--
src/app/settings/troubleshooting.tsx | 11 +--
src/app/signal/import.tsx | 13 ++-
src/app/signal/index.tsx | 17 ++--
src/app/signal/scan.tsx | 25 +++--
src/app/sources/edit.tsx | 20 ++--
src/app/sources/index.tsx | 12 +--
src/components/AppPressable.tsx | 95 +++++++++++++++++++
src/components/MiniPlayer.tsx | 60 ++++++------
src/components/PlaybackTargetPicker.tsx | 9 +-
src/components/ScreenHeader.tsx | 17 ++--
src/components/SegmentedControl.tsx | 10 +-
src/components/TabBar.tsx | 4 +-
src/components/appPressableFeedback.test.mts | 45 +++++++++
src/components/appPressableFeedback.ts | 55 +++++++++++
src/components/dialogs/AppDialog.tsx | 10 +-
src/components/eq/BandConsole.tsx | 30 +++---
src/components/eq/BandDetailPanel.tsx | 8 +-
src/components/eq/BandStrip.tsx | 12 +--
src/components/eq/EQPresetNameSheet.tsx | 12 +--
src/components/eq/EQPresetPreviewSheet.tsx | 12 +--
src/components/eq/EQPresetQrSheet.tsx | 8 +-
src/components/eq/EQSlider.tsx | 12 +--
src/components/eq/EQValueEditSheet.tsx | 12 +--
.../eq/PresetDeviceAssignmentSheet.tsx | 21 ++--
src/components/eq/PresetSheet.tsx | 17 ++--
src/components/eq/SavePresetSheet.tsx | 12 +--
src/components/library/AlbumGridItem.tsx | 8 +-
src/components/library/AlbumRow.tsx | 8 +-
src/components/library/ArtistGridItem.tsx | 8 +-
.../library/ArtistImageSearchSheet.tsx | 15 ++-
.../library/ArtistImageSweepStatus.tsx | 11 +--
src/components/library/ArtistRow.tsx | 8 +-
src/components/library/CollapsingDetail.tsx | 28 +++---
src/components/library/EmptyLibrary.tsx | 12 +--
src/components/library/FoldersView.tsx | 34 ++++---
src/components/library/LibraryContextBar.tsx | 17 ++--
src/components/library/PlaylistRow.tsx | 10 +-
.../ScanNotificationPermissionCard.tsx | 10 +-
src/components/library/ScanProgress.tsx | 11 +--
src/components/library/SelectionActionBar.tsx | 10 +-
src/components/library/TrackRow.tsx | 16 ++--
.../listening/ListeningPreviewCard.tsx | 11 +--
src/components/lyrics/LyricsBand.tsx | 15 ++-
src/components/lyrics/LyricsLine.tsx | 9 +-
src/components/lyrics/LyricsView.tsx | 19 ++--
.../ArtistImageDisclosurePrompt.tsx | 11 +--
src/components/onboarding/ArtistImageStep.tsx | 11 +--
.../onboarding/NotificationStep.tsx | 11 +--
src/components/onboarding/OnboardingFlow.tsx | 29 +++---
src/components/player/NowPlayingOverlay.tsx | 69 +++++++-------
src/components/player/SleepTimerControls.tsx | 27 +++---
src/components/queue/RemoteQueueSheet.tsx | 13 ++-
src/components/search/QuickSearchOverlay.tsx | 24 ++---
src/components/settings/AccentColorSheet.tsx | 16 ++--
src/components/settings/AccentSwatchRow.tsx | 15 ++-
src/components/settings/ScopeStyleCards.tsx | 11 +--
src/components/settings/SettingsPanels.tsx | 32 +++----
.../settings/SettingsSectionScaffold.tsx | 8 +-
src/components/sheets/ActionSheet.tsx | 9 +-
src/components/sheets/AppSheet.tsx | 9 +-
src/components/sheets/PlaylistPickerSheet.tsx | 12 +--
src/components/sheets/TextPromptModal.tsx | 11 +--
.../signal/SignalResolutionPanel.tsx | 35 ++++---
src/components/sync/SyncConflictPrompt.tsx | 19 ++--
src/theme/palettes.ts | 8 +-
src/theme/resolve.ts | 2 -
src/theme/ripple.ts | 53 -----------
89 files changed, 843 insertions(+), 835 deletions(-)
create mode 100644 src/components/AppPressable.tsx
create mode 100644 src/components/appPressableFeedback.test.mts
create mode 100644 src/components/appPressableFeedback.ts
delete mode 100644 src/theme/ripple.ts
diff --git a/eslint.config.js b/eslint.config.js
index ba708ed..bf4872b 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -6,5 +6,14 @@ module.exports = defineConfig([
expoConfig,
{
ignores: ["dist/*"],
+ rules: {
+ "no-restricted-syntax": [
+ "error",
+ {
+ selector: "JSXAttribute[name.name='android_ripple']",
+ message: "Use AppPressable feedback instead of the Android radial ripple.",
+ },
+ ],
+ },
}
]);
diff --git a/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueueContentView.kt b/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueueContentView.kt
index 2d4059b..7aedc77 100644
--- a/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueueContentView.kt
+++ b/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueueContentView.kt
@@ -9,7 +9,8 @@ import android.graphics.Path
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.RippleDrawable
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.StateListDrawable
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
@@ -1026,11 +1027,18 @@ class QueueContentView(
fun setSurfaceColor(color: Int) {
dividerColor = palette.divider
- background = RippleDrawable(
- ColorStateList.valueOf(palette.ripple),
- ColorDrawable(color),
- null,
- )
+ background = StateListDrawable().apply {
+ addState(
+ intArrayOf(android.R.attr.state_pressed),
+ LayerDrawable(
+ arrayOf(
+ ColorDrawable(color),
+ ColorDrawable(palette.pressOverlay),
+ ),
+ ),
+ )
+ addState(intArrayOf(), ColorDrawable(color))
+ }
}
override fun dispatchDraw(canvas: Canvas) {
diff --git a/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueuePalette.kt b/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueuePalette.kt
index 88bdf78..c4de8ee 100644
--- a/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueuePalette.kt
+++ b/modules/astra-library-scanner/android/src/main/java/expo/modules/astralibraryscanner/queue/QueuePalette.kt
@@ -9,7 +9,7 @@ data class QueuePalette(
val selectedSurface: Int = Color.rgb(57, 62, 75),
val nowPlayingSurface: Int = Color.rgb(31, 34, 43),
val divider: Int = Color.rgb(72, 72, 82),
- val ripple: Int = Color.argb(38, 140, 162, 208),
+ val pressOverlay: Int = Color.argb(20, 140, 162, 208),
val text: Int = Color.WHITE,
val textSecondary: Int = Color.rgb(190, 190, 200),
val textTertiary: Int = Color.rgb(135, 135, 148),
@@ -30,7 +30,7 @@ data class QueuePalette(
selectedSurface = color("selectedSurface", defaults.selectedSurface),
nowPlayingSurface = color("nowPlayingSurface", defaults.nowPlayingSurface),
divider = color("divider", defaults.divider),
- ripple = color("ripple", defaults.ripple),
+ pressOverlay = color("pressOverlay", defaults.pressOverlay),
text = color("text", defaults.text),
textSecondary = color("textSecondary", defaults.textSecondary),
textTertiary = color("textTertiary", defaults.textTertiary),
diff --git a/modules/astra-library-scanner/queue.ts b/modules/astra-library-scanner/queue.ts
index b02dd16..9a2d7dc 100644
--- a/modules/astra-library-scanner/queue.ts
+++ b/modules/astra-library-scanner/queue.ts
@@ -13,7 +13,7 @@ export interface NativeQueuePalette {
selectedSurface: number;
nowPlayingSurface: number;
divider: number;
- ripple: number;
+ pressOverlay: number;
text: number;
textSecondary: number;
textTertiary: number;
@@ -93,7 +93,7 @@ export function toNativeQueuePalette(colors: Palette): NativeQueuePalette {
selectedSurface: nativeColor(colors.glassHighlight),
nowPlayingSurface: nativeColor(colors.glassBg),
divider: nativeColor(colors.glassBorder),
- ripple: nativeColor(colors.ripple),
+ pressOverlay: nativeColor(colors.glassHighlight),
text: nativeColor(colors.textPrimary),
textSecondary: nativeColor(colors.textSecondary),
textTertiary: nativeColor(colors.textTertiary),
diff --git a/package.json b/package.json
index 0edb5e1..743ae03 100644
--- a/package.json
+++ b/package.json
@@ -98,6 +98,7 @@
"test:app-dialog": "node --experimental-strip-types --test src/components/dialogs/dialogQueue.test.mts",
"test:home-greeting": "node --experimental-strip-types --test src/home/homeGreeting.test.mts src/home/homeLayout.test.mts",
"test:theme": "node --experimental-strip-types --experimental-specifier-resolution=node --test src/theme/accentColors.test.mts src/theme/artworkAccentMath.test.mts",
+ "test:press-feedback": "node --experimental-strip-types --test src/components/appPressableFeedback.test.mts",
"test:session": "node --experimental-strip-types --test src/session/sessionState.test.mts src/session/playbackMaterialization.test.mts",
"test:library-scan": "node --experimental-strip-types --test src/library/scanCancellation.test.mts",
"test:release-config": "node --experimental-strip-types --test plugins/withAstraAndroidRelease.test.mjs scripts/release/android-release.test.mjs src/release/buildInfo.test.mts",
diff --git a/scripts/run-release-tests.mjs b/scripts/run-release-tests.mjs
index 5da0685..8d13163 100644
--- a/scripts/run-release-tests.mjs
+++ b/scripts/run-release-tests.mjs
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
const TEST_SCRIPTS = [
'test:release-config',
+ 'test:press-feedback',
'test:queue-actions',
'test:desktop-remote',
'test:dynamic-playlists',
diff --git a/src/app/(tabs)/eq.tsx b/src/app/(tabs)/eq.tsx
index 31ffb81..0ca4fec 100644
--- a/src/app/(tabs)/eq.tsx
+++ b/src/app/(tabs)/eq.tsx
@@ -1,7 +1,6 @@
import { useCallback, useState } from 'react';
import {
InteractionManager,
- Pressable,
StyleSheet,
View,
useWindowDimensions
@@ -43,7 +42,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { hapticForToggle } from '@/lib/hapticCatalog';
import { playHaptic } from '@/lib/haptics';
import { useAppForeground } from '@/lib/useAppForeground';
@@ -99,7 +98,6 @@ const BAND_TYPES: EQBandType[] = ['lowshelf', 'peaking', 'highshelf', 'highpass'
export default function EQScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const pathname = usePathname();
@@ -309,7 +307,7 @@ export default function EQScreen() {
};
const presetRowEl = (
- setSheet('preset')}
>
@@ -317,7 +315,7 @@ export default function EQScreen() {
{presetName}
-
+
);
const modeSwitcherEl = (
@@ -436,7 +434,7 @@ export default function EQScreen() {
onValuePress={() => setEditingValue('preamp')}
/>
- {
playHaptic(hapticForToggle(!eq.enabled));
@@ -451,7 +449,7 @@ export default function EQScreen() {
{eq.enabled ? 'EQ on' : 'EQ off'}
-
+
);
@@ -470,12 +468,12 @@ export default function EQScreen() {
- setSheet('save')} hitSlop={8}>
+ setSheet('save')} hitSlop={8}>
-
- setSheet('overflow')} hitSlop={8}>
+
+ setSheet('overflow')} hitSlop={8}>
-
+
diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx
index 02f16ac..8a70a70 100644
--- a/src/app/(tabs)/index.tsx
+++ b/src/app/(tabs)/index.tsx
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import {
AppState,
- Pressable,
ScrollView,
StyleSheet,
View,
@@ -33,7 +32,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { useLibraryStore } from '@/stores/libraryStore';
import { usePlaylistStore } from '@/stores/playlistStore';
import { usePlayerStore } from '@/stores/playerStore';
@@ -144,7 +143,6 @@ function HomeMasthead({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const [clockNow, setClockNow] = useState(() => new Date());
const [greeting, setGreeting] = useState(() => chooseHomeGreeting(null, new Date()));
@@ -190,9 +188,9 @@ function HomeMasthead({
}, [mode]);
const searchButton = (
-
-
+
);
const scanButton = (
-
-
+
);
const utilityButtons = (
@@ -265,7 +263,6 @@ function SectionHeader({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
@@ -279,12 +276,12 @@ function SectionHeader({
) : null}
{onActionPress && actionLabel ? (
-
+
{actionLabel}
-
+
) : null}
);
@@ -355,9 +352,8 @@ function RecentlyAddedAlbum({
onPress: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
return (
-
+
{album.album}
@@ -365,7 +361,7 @@ function RecentlyAddedAlbum({
{album.artist}
-
+
);
}
@@ -390,7 +386,6 @@ function RandomSpotlightCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const disabled = !hasTracks;
const title = spotlight.kind === 'album' ? spotlight.album.album : spotlight.artist.artist;
const label = spotlight.kind === 'album' ? 'RANDOM ALBUM' : 'RANDOM ARTIST';
@@ -403,9 +398,9 @@ function RandomSpotlightCard({
};
return (
-
-
-
-
+
-
-
+ runAction(event, onReroll)}
@@ -462,11 +457,11 @@ function RandomSpotlightCard({
accessibilityLabel="Pick another random album or artist"
>
-
+
-
+
);
}
@@ -481,7 +476,6 @@ function EmptyHomeCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const fatal = status === 'fatalUserData';
const rebuilding = status === 'rebuilding';
const degraded = status === 'degraded';
@@ -517,8 +511,8 @@ function EmptyHomeCard({
) : null}
-
{fatal ? 'Troubleshooting' : 'Folder settings'}
-
+
);
}
diff --git a/src/app/(tabs)/library/artist/[name].tsx b/src/app/(tabs)/library/artist/[name].tsx
index 57e1915..5aa93cb 100644
--- a/src/app/(tabs)/library/artist/[name].tsx
+++ b/src/app/(tabs)/library/artist/[name].tsx
@@ -4,7 +4,6 @@ import {
type ComponentProps
} from 'react';
import {
- Pressable,
ScrollView,
StyleSheet,
View
@@ -30,7 +29,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import type { Palette } from '@/theme/palettes';
import { usePlayerStore } from '@/stores/playerStore';
import { useSettingsStore } from '@/stores/settingsStore';
@@ -486,7 +485,6 @@ function SectionHeader({
onPress?: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
@@ -499,12 +497,12 @@ function SectionHeader({
{onPress ? (
-
+
See all
-
+
) : null}
);
@@ -518,7 +516,6 @@ function AlbumRail({
onAlbumPress: (album: ArtistAlbum) => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
return (
{albums.map((album) => (
- onAlbumPress(album)}
@@ -552,7 +549,7 @@ function AlbumRail({
.filter(Boolean)
.join(' - ')}
-
+
))}
);
diff --git a/src/app/(tabs)/library/index.tsx b/src/app/(tabs)/library/index.tsx
index 91dc06f..76ec5b2 100644
--- a/src/app/(tabs)/library/index.tsx
+++ b/src/app/(tabs)/library/index.tsx
@@ -11,7 +11,6 @@ import {
ActivityIndicator,
BackHandler,
View,
- Pressable,
StyleSheet,
useWindowDimensions
} from 'react-native';
@@ -64,7 +63,7 @@ import {
import { spacing } from '@/theme';
import { motion } from '@/theme/motion';
import { useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useShellLayout } from '@/navigation/useShellLayout';
import { useTabReselect } from '@/navigation/useTabReselect';
import { shouldAnimateScrollToTop } from '@/navigation/scrollToTopBehavior';
@@ -166,7 +165,6 @@ function libraryChromeHeight(options: {
export default function LibraryScreen() {
const colors = useColors();
- const ripple = useRipple();
const router = useRouter();
const viewMode = useLibraryStore((s) => s.viewMode);
const setViewMode = useLibraryStore((s) => s.setViewMode);
@@ -684,7 +682,7 @@ export default function LibraryScreen() {
{/* The rail already names this destination, so in landscape there is no
title row to carry search and it moves in beside the switcher. */}
{!showScreenTitle ? (
- openQuickSearch()}
@@ -692,7 +690,7 @@ export default function LibraryScreen() {
accessibilityLabel="Search library"
>
-
+
) : null}
@@ -717,15 +715,15 @@ export default function LibraryScreen() {
{selectedIds.size} selected
-
+
Cancel
-
+
) : sortable ? (
- setSortSheetOpen(true)}
accessibilityRole="button"
@@ -733,10 +731,10 @@ export default function LibraryScreen() {
>
{sortLabel}
-
+
{activeLayout && activeLayoutLabel ? (
- setLayoutSheetOpen(true)}
@@ -748,7 +746,7 @@ export default function LibraryScreen() {
size={18}
color={colors.textSecondary}
/>
-
+
) : null}
) : null}
diff --git a/src/app/(tabs)/library/playlist/[id].tsx b/src/app/(tabs)/library/playlist/[id].tsx
index 4880809..5a2e050 100644
--- a/src/app/(tabs)/library/playlist/[id].tsx
+++ b/src/app/(tabs)/library/playlist/[id].tsx
@@ -5,7 +5,6 @@ import {
} from 'react';
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -27,7 +26,7 @@ import { showAppDialog } from '@/components/dialogs/AppDialog';
import { CollapsingHeader, useDetailCollapse } from '@/components/library/CollapsingDetail';
import { spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { usePlaylistStore } from '@/stores/playlistStore';
import { usePlayerStore } from '@/stores/playerStore';
import { playLibraryQuery } from '@/audio/playbackController';
@@ -56,11 +55,10 @@ function basename(path: string): string {
function MissingRow({ entry, onLongPress }: { entry: PlaylistTrackEntry; onLongPress: () => void }) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
- {
@@ -78,7 +76,7 @@ function MissingRow({ entry, onLongPress }: { entry: PlaylistTrackEntry; onLongP
-
+
);
}
@@ -87,7 +85,6 @@ type Prompt = { kind: 'rename'; playlist: Playlist } | null;
export default function PlaylistScreen() {
const sceneBottomInset = useSceneBottomInset();
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>();
@@ -328,7 +325,7 @@ export default function PlaylistScreen() {
heroMeta={{meta}}
heroExtra={
isDynamic && playlistId != null ? (
-
router.push({
@@ -343,7 +340,7 @@ export default function PlaylistScreen() {
Rules
-
+
) : null
}
disabled={playable.length === 0}
diff --git a/src/app/(tabs)/library/playlist/edit-dynamic.tsx b/src/app/(tabs)/library/playlist/edit-dynamic.tsx
index 84b5f9a..37f52b0 100644
--- a/src/app/(tabs)/library/playlist/edit-dynamic.tsx
+++ b/src/app/(tabs)/library/playlist/edit-dynamic.tsx
@@ -6,7 +6,6 @@ import {
import {
KeyboardAvoidingView,
Platform,
- Pressable,
ScrollView,
StyleSheet,
TextInput,
@@ -27,7 +26,7 @@ import {
} from '@/components/sheets/AppSheet';
import { fonts, fontSize, radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { usePlaylistStore } from '@/stores/playlistStore';
import {
DYNAMIC_PLAYLIST_PRESETS,
@@ -329,16 +328,15 @@ function DraftActions({
onApply: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
-
+
Cancel
-
-
+
Apply
-
+
);
}
@@ -532,7 +530,6 @@ function ConditionEditorSheet({
onApply: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const draft = target.draft;
const error = validateCondition(draft);
@@ -540,7 +537,7 @@ function ConditionEditorSheet({
return (
-
+
FIELD
@@ -548,7 +545,7 @@ function ConditionEditorSheet({
{fieldLabel(draft)}
-
+
@@ -572,12 +569,12 @@ function ConditionEditorSheet({
{target.mode === 'edit' ? (
-
+
Remove
-
+
) : (
)}
@@ -717,12 +714,11 @@ function FilterCard({
onRemove: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const option = fieldOptionForKey(getConditionFieldKey(condition));
return (
-
+
@@ -734,7 +730,7 @@ function FilterCard({
{fieldGroupLabel(condition)}
-
-
-
+
+
);
}
export default function DynamicPlaylistEditorScreen() {
const sceneBottomInset = useSceneBottomInset();
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const { id } = useLocalSearchParams<{ id?: string }>();
@@ -1053,7 +1048,7 @@ export default function DynamicPlaylistEditorScreen() {
>
- router.back()}
hitSlop={8}
style={styles.headerButton}
@@ -1061,7 +1056,7 @@ export default function DynamicPlaylistEditorScreen() {
accessibilityLabel="Back"
>
-
+
{isEditing ? 'Edit dynamic playlist' : 'New dynamic playlist'}
@@ -1097,7 +1092,7 @@ export default function DynamicPlaylistEditorScreen() {
contentContainerStyle={styles.presetRow}
>
{DYNAMIC_PLAYLIST_PRESETS.map((preset) => (
- applyPreset(preset.rules)}
@@ -1107,7 +1102,7 @@ export default function DynamicPlaylistEditorScreen() {
{preset.label}
-
+
))}
@@ -1117,12 +1112,12 @@ export default function DynamicPlaylistEditorScreen() {
FILTERS
- openFieldPicker('new')} accessibilityRole="button">
+ openFieldPicker('new')} accessibilityRole="button">
Add filter
-
+
{rules.conditions.length === 0 ? (
@@ -1150,7 +1145,7 @@ export default function DynamicPlaylistEditorScreen() {
ORDER
-
+
@@ -1161,7 +1156,7 @@ export default function DynamicPlaylistEditorScreen() {
-
+
@@ -1180,7 +1175,7 @@ export default function DynamicPlaylistEditorScreen() {
{status.label}
- setSheet({ kind: 'preview' })}
@@ -1190,8 +1185,8 @@ export default function DynamicPlaylistEditorScreen() {
Preview
-
-
+
{isSaving ? 'Saving' : 'Save'}
-
+
{renderSheet()}
diff --git a/src/app/(tabs)/stats.tsx b/src/app/(tabs)/stats.tsx
index 4034b5c..46b51ad 100644
--- a/src/app/(tabs)/stats.tsx
+++ b/src/app/(tabs)/stats.tsx
@@ -22,7 +22,7 @@ import { useListeningStatsStore } from '@/stores/listeningStatsStore';
import { playLibraryQuery } from '@/audio/playbackController';
import { fonts, radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type {
ListeningStatsCategory,
ListeningStatsDashboard,
@@ -136,13 +136,12 @@ function RankingRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const copy = rankingCopy(item, category);
const art = listeningArtworkSource(item, true);
return (
-
-
+
);
}
@@ -206,16 +205,15 @@ function EmptyState({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
{title}
{body}
{action && onAction ? (
-
+
{action}
-
+
) : null}
);
diff --git a/src/app/desktop-remote.tsx b/src/app/desktop-remote.tsx
index 024a9eb..4a29286 100644
--- a/src/app/desktop-remote.tsx
+++ b/src/app/desktop-remote.tsx
@@ -7,7 +7,6 @@ import {
ActivityIndicator,
KeyboardAvoidingView,
Platform,
- Pressable,
ScrollView,
StyleSheet,
TextInput,
@@ -28,7 +27,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { isWideWindow, WIDE_MIN_WIDTH } from '@/theme/adaptive';
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
import { usePlayerUiStore } from '@/stores/playerUiStore';
@@ -192,10 +191,9 @@ function DiscoveredDesktopRow({ desktop, onPair, disabled }: {
disabled: boolean;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
- onPair(desktop)}
disabled={disabled}
@@ -217,13 +215,12 @@ function DiscoveredDesktopRow({ desktop, onPair, disabled }: {
-
+
);
}
export default function DesktopRemoteScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const returnToTabs = useReturnToTabs();
@@ -436,7 +433,7 @@ export default function DesktopRemoteScreen() {
maxLength={6}
textContentType="oneTimeCode"
/>
-
Confirm PIN
-
+
) : null}
@@ -462,12 +459,12 @@ export default function DesktopRemoteScreen() {
Open Astra Desktop settings, enable Phone Remote, then scan or paste the pairing link.
- router.push('/desktop-remote/scan' as never)}>
+ router.push('/desktop-remote/scan' as never)}>
Scan QR
-
+
- void pairFromInput(pairingLink)}
@@ -487,7 +484,7 @@ export default function DesktopRemoteScreen() {
Pair from link
-
+
@@ -507,7 +504,7 @@ export default function DesktopRemoteScreen() {
autoCorrect={false}
keyboardType="url"
/>
-
Request secure PIN
-
+
{pinPairing ? (
@@ -560,9 +557,9 @@ export default function DesktopRemoteScreen() {
>
- router.back()} hitSlop={12}>
+ router.back()} hitSlop={12}>
-
+
DESKTOP REMOTE
@@ -571,14 +568,14 @@ export default function DesktopRemoteScreen() {
{remoteSource}
- void reconnect()}
hitSlop={12}
accessibilityLabel="Reconnect to desktop"
>
-
+
@@ -619,7 +616,7 @@ export default function DesktopRemoteScreen() {
- {
// The player is an overlay, not a route: open it and pop this
@@ -633,17 +630,17 @@ export default function DesktopRemoteScreen() {
Open Now Playing
-
- void reconnect()}>
+
+ void reconnect()}>
Reconnect
-
-
+
+
Forget desktop
-
+
@@ -673,12 +670,12 @@ export default function DesktopRemoteScreen() {
) : (
<>
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Settings
-
+
{renderSetup()}
>
diff --git a/src/app/desktop-remote/scan.tsx b/src/app/desktop-remote/scan.tsx
index 287a01d..7d98439 100644
--- a/src/app/desktop-remote/scan.tsx
+++ b/src/app/desktop-remote/scan.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react';
import {
ActivityIndicator,
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -19,12 +18,11 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
export default function DesktopRemoteScanScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const pairFromInput = useDesktopRemoteStore((s) => s.pairFromInput);
@@ -44,12 +42,12 @@ export default function DesktopRemoteScanScreen() {
return (
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Desktop Remote
-
+
@@ -64,11 +62,11 @@ export default function DesktopRemoteScanScreen() {
Camera access is needed to scan the desktop pairing QR.
- void requestPermission()}>
+ void requestPermission()}>
Allow camera
-
+
) : (
diff --git a/src/app/desktop-sync.tsx b/src/app/desktop-sync.tsx
index d91d019..ba8b375 100644
--- a/src/app/desktop-sync.tsx
+++ b/src/app/desktop-sync.tsx
@@ -7,7 +7,6 @@
import { useEffect, useState } from 'react';
import {
ActivityIndicator,
- Pressable,
ScrollView,
StyleSheet,
View,
@@ -20,7 +19,7 @@ import { HapticSwitch } from '@/components/HapticSwitch';
import { SyncConflictDetails } from '@/components/sync/SyncConflictDetails';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { formatRelativeTime } from '@/lib/format';
import { getDesktopRemoteConnection } from '@/services/desktopRemoteCredentials';
import { useDesktopSyncStore } from '@/stores/desktopSyncStore';
@@ -68,7 +67,6 @@ function ConflictCard({
onResolve: (resolution: DesktopSyncConflictResolution) => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [selectedResolution, setSelectedResolution] = useState(null);
const desktopSnapshot = syncPlaylistToSnapshot(conflict.remote);
@@ -87,7 +85,7 @@ function ConflictCard({
{options.map((resolution) => (
- setSelectedResolution(resolution)}
>
{RESOLUTION_LABELS[resolution]}
-
+
))}
@@ -116,7 +114,7 @@ function ConflictCard({
previewResolution={selectedResolution}
/>
- selectedResolution ? onResolve(selectedResolution) : undefined}
@@ -124,7 +122,7 @@ function ConflictCard({
Confirm
-
+
);
@@ -132,7 +130,6 @@ function ConflictCard({
export default function DesktopSyncScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const status = useDesktopSyncStore((s) => s.status);
@@ -175,12 +172,12 @@ export default function DesktopSyncScreen() {
return (
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Settings
-
+
@@ -204,7 +201,7 @@ export default function DesktopSyncScreen() {
Sync uses the same pairing as the Desktop Remote. Pair this phone with Astra Desktop
once and both features work.
- router.push('/desktop-remote' as never)}
>
@@ -212,7 +209,7 @@ export default function DesktopSyncScreen() {
Pair with a desktop
-
+
) : (
<>
@@ -228,7 +225,7 @@ export default function DesktopSyncScreen() {
: 'Not synced yet'}
- void syncNow()}
@@ -242,7 +239,7 @@ export default function DesktopSyncScreen() {
Sync now
-
+
{summaryLine ? (
diff --git a/src/app/eq/import.tsx b/src/app/eq/import.tsx
index 39141af..a70c4a5 100644
--- a/src/app/eq/import.tsx
+++ b/src/app/eq/import.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useLocalSearchParams } from 'expo-router';
import { useReturnToTabs } from '@/navigation/returnToTabs';
@@ -11,12 +11,11 @@ import { genEqId } from '@/audio/eqPresets';
import { useEQStore } from '@/stores/eqStore';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { EQPreset } from '@/types/audio';
export default function EQPresetImportScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const returnToTabs = useReturnToTabs();
const importPreset = useEQStore((state) => state.importPreset);
@@ -39,21 +38,21 @@ export default function EQPresetImportScreen() {
return (
-
+
Equalizer
-
+
This link does not contain a valid Astra EQ preset.
-
+
Go to Equalizer
-
+
);
diff --git a/src/app/eq/scan.tsx b/src/app/eq/scan.tsx
index 50ff7b4..a19a562 100644
--- a/src/app/eq/scan.tsx
+++ b/src/app/eq/scan.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -23,12 +22,11 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { EQPreset } from '@/types/audio';
export default function EQPresetScanScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const returnToTabs = useReturnToTabs();
@@ -53,12 +51,12 @@ export default function EQPresetScanScreen() {
return (
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Equalizer
-
+
@@ -71,11 +69,11 @@ export default function EQPresetScanScreen() {
Camera access is needed to scan EQ preset QR codes.
- void requestPermission()}>
+ void requestPermission()}>
Allow camera
-
+
) : (
@@ -94,11 +92,11 @@ export default function EQPresetScanScreen() {
{error}
- setError(null)}>
+ setError(null)}>
Scan again
-
+
) : null}
diff --git a/src/app/lastfm/edit.tsx b/src/app/lastfm/edit.tsx
index 9689292..8ae4b06 100644
--- a/src/app/lastfm/edit.tsx
+++ b/src/app/lastfm/edit.tsx
@@ -3,7 +3,6 @@ import {
ActivityIndicator,
KeyboardAvoidingView,
Platform,
- Pressable,
ScrollView,
StyleSheet,
TextInput,
@@ -19,7 +18,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useLastFmSettingsStore } from '@/stores/lastFmSettingsStore';
import type { LastFmScrobbleProtocol } from '@/types/lastFm';
@@ -104,7 +103,6 @@ function Field({
export default function LastFmEditScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const { id } = useLocalSearchParams<{ id?: string }>();
@@ -202,12 +200,12 @@ export default function LastFmEditScreen() {
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
-
+
{backLabel}
-
+
{step === 'type' ? (
@@ -221,7 +219,7 @@ export default function LastFmEditScreen() {
{PROTOCOL_OPTIONS.map((option) => (
- chooseProtocol(option.protocol)}
@@ -241,7 +239,7 @@ export default function LastFmEditScreen() {
-
+
))}
@@ -295,7 +293,7 @@ export default function LastFmEditScreen() {
) : null}
- void onSave()}
disabled={!canSubmit || busy}
@@ -307,15 +305,15 @@ export default function LastFmEditScreen() {
{editing ? 'Save' : 'Add destination'}
)}
-
+
{editing ? (
-
+
Remove destination
-
+
) : null}
)}
diff --git a/src/app/lastfm/index.tsx b/src/app/lastfm/index.tsx
index c8ffbbd..bc42e52 100644
--- a/src/app/lastfm/index.tsx
+++ b/src/app/lastfm/index.tsx
@@ -1,6 +1,5 @@
import { useEffect } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -21,7 +20,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useLastFmSettingsStore } from '@/stores/lastFmSettingsStore';
import { requestLastFmFlush } from '@/services/lastfm';
import type { LastFmProfileStatus } from '@/types/lastFm';
@@ -51,7 +50,6 @@ function customStatusLine(profile: LastFmProfileStatus): { text: string; tone: '
export default function LastFmScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const header = useScreenHeader({ actionCount: 1 });
@@ -137,16 +135,16 @@ export default function LastFmScreen() {
{profile.username}
) : null}
- confirmDisconnect(profile)}
hitSlop={8}
accessibilityLabel="Disconnect Last.fm"
>
-
+
) : (
- connectOfficial(profile)}
accessibilityRole="button"
@@ -155,7 +153,7 @@ export default function LastFmScreen() {
Connect
-
+
)}
);
@@ -164,7 +162,7 @@ export default function LastFmScreen() {
const renderCustom = (profile: LastFmProfileStatus) => {
const line = customStatusLine(profile);
return (
- router.push({ pathname: '/lastfm/edit', params: { id: profile.id } })}
@@ -200,7 +198,7 @@ export default function LastFmScreen() {
) : (
)}
-
+
);
};
@@ -248,12 +246,12 @@ export default function LastFmScreen() {
) : null}
{status && status.pendingScrobbles > 0 ? (
- requestLastFmFlush()}>
+ requestLastFmFlush()}>
Retry {status.pendingScrobbles} queued now
-
+
) : null}
- router.push('/lastfm/edit')}>
+ router.push('/lastfm/edit')}>
Add destination
-
+
A scrobble is sent once a track plays past half its length (or 4 minutes). Tracks under 30
diff --git a/src/app/settings/haptics-lab.tsx b/src/app/settings/haptics-lab.tsx
index 002a6c1..c4117d3 100644
--- a/src/app/settings/haptics-lab.tsx
+++ b/src/app/settings/haptics-lab.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import {
SettingsCard,
SettingsSectionLabel,
@@ -8,7 +8,7 @@ import {
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { playHaptic, type HapticEvent } from '@/lib/haptics';
import {
HAPTIC_RECIPE_SECTIONS,
@@ -60,7 +60,6 @@ function yesNo(value: boolean): string {
export default function HapticsLabScreen() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const [capabilities, setCapabilities] = useState(() =>
AstraHaptics.getCapabilities()
);
@@ -97,14 +96,14 @@ export default function HapticsLabScreen() {
label="Touch feedback enabled"
value={yesNo(capabilities.touchFeedbackEnabled)}
/>
-
Refresh capabilities
-
+
SEMANTIC VOCABULARY
@@ -259,10 +258,9 @@ function AuditionButton({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
-
{label}
-
+
);
}
diff --git a/src/app/settings/playback.tsx b/src/app/settings/playback.tsx
index aa6aa7a..d1976a6 100644
--- a/src/app/settings/playback.tsx
+++ b/src/app/settings/playback.tsx
@@ -1,5 +1,5 @@
import { SleepTimerControls } from '@/components/player/SleepTimerControls';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { AstraLibraryData } from '../../../modules/astra-library-scanner';
import {
@@ -17,13 +17,12 @@ import {
import { useSettingsStore } from '@/stores/settingsStore';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { notifyListeningHistoryChanged } from '@/listeningStats/events';
export default function PlaybackSettingsScreen() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const historyEnabled = useSettingsStore((s) => s.listeningHistoryEnabled);
const setHistoryEnabled = useSettingsStore((s) => s.setListeningHistoryEnabled);
@@ -83,8 +82,8 @@ export default function PlaybackSettingsScreen() {
}}
/>
-
-
+
);
diff --git a/src/app/settings/troubleshooting.tsx b/src/app/settings/troubleshooting.tsx
index 60c6dff..c5a29d2 100644
--- a/src/app/settings/troubleshooting.tsx
+++ b/src/app/settings/troubleshooting.tsx
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
-import { ActivityIndicator, Pressable, View } from 'react-native';
+import { ActivityIndicator, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import { Text } from '@/components/Text';
@@ -21,7 +21,7 @@ import { useLibraryStore } from '@/stores/libraryStore';
import { useOnboardingStore } from '@/stores/onboardingStore';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
type ActionKey = 'scan' | 'rebuild' | 'lyrics' | 'waveform' | 'onboarding';
@@ -255,11 +255,10 @@ function MaintenanceRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
- {description}
{running ? : }
-
+
);
}
diff --git a/src/app/signal/import.tsx b/src/app/signal/import.tsx
index aeb7c12..65ec48b 100644
--- a/src/app/signal/import.tsx
+++ b/src/app/signal/import.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Screen } from '@/components/Screen';
@@ -8,12 +8,11 @@ import { SignalResultCard } from '@/components/signal/SignalResultCard';
import { decodeTrackSignalLink, SIGNAL_LINK_PREFIX } from '@/audio/signalShare';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { SignalPayload } from '@boof2015/astra-signal';
export default function SignalImportScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const { data } = useLocalSearchParams<{ data?: string }>();
@@ -32,12 +31,12 @@ export default function SignalImportScreen() {
return (
-
+
Signal
-
+
{payload ? (
@@ -46,11 +45,11 @@ export default function SignalImportScreen() {
This link does not contain a valid Astra Signal.
-
+
Go to Signal
-
+
)}
diff --git a/src/app/signal/index.tsx b/src/app/signal/index.tsx
index de11edc..637b7a6 100644
--- a/src/app/signal/index.tsx
+++ b/src/app/signal/index.tsx
@@ -1,5 +1,5 @@
import { useMemo, useRef } from 'react';
-import { Pressable, Share, StyleSheet, useWindowDimensions, View } from 'react-native';
+import { Share, StyleSheet, useWindowDimensions, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import { cacheDirectory, EncodingType, writeAsStringAsync } from 'expo-file-system/legacy';
@@ -13,7 +13,7 @@ import { playerBackdropArtworkSource } from '@/library/artwork';
import { usePlayerStore } from '@/stores/playerStore';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
// Keep the device presentation identical to the canonical shared PNG. The
// near-white isolation field is part of the proven phone-to-phone geometry.
@@ -23,7 +23,6 @@ const CODE_BG = '#f4f4f6';
export default function SignalScreen() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const router = useRouter();
const track = usePlayerStore((s) => s.currentTrack);
const codeRef = useRef(null);
@@ -65,12 +64,12 @@ export default function SignalScreen() {
offset={{ top: 0, left: -spacing.lg, right: -spacing.lg }}
/>
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Back
-
+
@@ -116,16 +115,16 @@ export default function SignalScreen() {
- void shareImage()}>
+ void shareImage()}>
Share image
-
- void shareLink()}>
+
+ void shareLink()}>
Share link
-
+
)}
diff --git a/src/app/signal/scan.tsx b/src/app/signal/scan.tsx
index c86498b..1ed185f 100644
--- a/src/app/signal/scan.tsx
+++ b/src/app/signal/scan.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
-import { Linking, Pressable, StyleSheet, View } from 'react-native';
+import { Linking, StyleSheet, View } from 'react-native';
import { CameraView, useCameraPermissions } from 'expo-camera';
import * as DocumentPicker from 'expo-document-picker';
import { Ionicons } from '@expo/vector-icons';
@@ -23,7 +23,7 @@ import { dbTrackToTrack } from '@/library/trackAdapter';
import { playHaptic } from '@/lib/haptics';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { SignalPayload } from '@boof2015/astra-signal';
import type { DbTrack } from '@/types/library';
import { AstraLibraryData } from '../../../modules/astra-library-scanner';
@@ -33,7 +33,6 @@ const FAILURE_RETURN_MS = 480;
export default function SignalScanScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const cameraRef = useRef(null);
@@ -200,12 +199,12 @@ export default function SignalScanScreen() {
return (
- router.back()} hitSlop={8}>
+ router.back()} hitSlop={8}>
Back
-
+
@@ -227,16 +226,16 @@ export default function SignalScanScreen() {
Camera access is needed to scan a Signal.
- void requestPermission()}>
+ void requestPermission()}>
Allow camera
-
- void pickImage()}>
+
+ void pickImage()}>
Or pick a Signal image
-
+
) : (
@@ -253,12 +252,12 @@ export default function SignalScanScreen() {
- void pickImage()} hitSlop={8}>
+ void pickImage()} hitSlop={8}>
-
- void capture()} hitSlop={8}>
+
+ void capture()} hitSlop={8}>
-
+
();
@@ -189,12 +187,12 @@ export default function SourceEditScreen() {
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
-
+
{backLabel}
-
+
{step === 'type' ? (
@@ -208,7 +206,7 @@ export default function SourceEditScreen() {
{TYPE_OPTIONS.map((option) => (
- chooseType(option.type)}
@@ -228,7 +226,7 @@ export default function SourceEditScreen() {
-
+
))}
@@ -276,7 +274,7 @@ export default function SourceEditScreen() {
) : null}
- void onTest()}
disabled={!!busy}
@@ -288,8 +286,8 @@ export default function SourceEditScreen() {
Test connection
)}
-
-
+
)}
-
+
)}
diff --git a/src/app/sources/index.tsx b/src/app/sources/index.tsx
index 610aef2..1e82d41 100644
--- a/src/app/sources/index.tsx
+++ b/src/app/sources/index.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -21,7 +20,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useRemoteSourcesStore } from '@/stores/remoteSourcesStore';
import type { RemoteSourceRow, RemoteSyncProgress } from '@/types/remote';
@@ -45,7 +44,6 @@ function statusLine(
export default function SourcesScreen() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const sources = useRemoteSourcesStore((s) => s.sources);
@@ -125,18 +123,18 @@ export default function SourcesScreen() {
No servers yet. Add a Subsonic or Jellyfin server to stream and browse your
self-hosted library.
- router.push('/sources/edit')}>
+ router.push('/sources/edit')}>
Add server
-
+
) : (
sources.map((source) => {
const status = statusLine(source, progressById[source.id] ?? null);
return (
- setActionFor(source)}
@@ -170,7 +168,7 @@ export default function SourcesScreen() {
-
+
);
})
)}
diff --git a/src/components/AppPressable.tsx b/src/components/AppPressable.tsx
new file mode 100644
index 0000000..dd4ef6f
--- /dev/null
+++ b/src/components/AppPressable.tsx
@@ -0,0 +1,95 @@
+import { Fragment, forwardRef } from 'react';
+import {
+ Pressable as NativePressable,
+ StyleSheet,
+ View,
+ type PressableProps,
+ type PressableStateCallbackType,
+ type View as NativeView,
+ type ViewStyle,
+} from 'react-native';
+import { useColors } from '@/theme/themed';
+import {
+ composePressedStyle,
+ resolveFeedbackCorners,
+ resolvePressFeedback,
+ type AppPressFeedback,
+ type FeedbackCornerSource,
+} from '@/components/appPressableFeedback';
+
+/**
+ * Delay used only inside scrollable content. A scroll gesture claims the touch
+ * before feedback appears, while deliberate taps still receive press feedback.
+ */
+export const SCROLL_PRESS_DELAY = 80;
+
+export interface AppPressableProps extends Omit {
+ /** Astra's non-radial Android press treatment. */
+ feedback?: AppPressFeedback;
+ /** Overrides radii inferred from the root style for surface/tile washes. */
+ feedbackRadius?: number;
+}
+
+/**
+ * The app-wide Android press surface. It preserves NativePressable semantics
+ * while replacing the platform ripple with a restrained wash or opacity shift.
+ */
+export const AppPressable = forwardRef(
+ function AppPressable(
+ {
+ feedback = 'surface',
+ feedbackRadius,
+ style,
+ children,
+ disabled = false,
+ ...rest
+ },
+ ref,
+ ) {
+ const colors = useColors();
+ const isDisabled = disabled === true;
+
+ const resolveStyle = (state: PressableStateCallbackType) =>
+ typeof style === 'function' ? style(state) : style;
+
+ return (
+
+ composePressedStyle(
+ resolveStyle(state),
+ resolvePressFeedback(feedback, state.pressed, isDisabled),
+ )
+ }
+ >
+ {(state) => {
+ const content = typeof children === 'function' ? children(state) : children;
+ const decision = resolvePressFeedback(feedback, state.pressed, isDisabled);
+ if (!decision.overlay) return content;
+
+ const flattened = StyleSheet.flatten(resolveStyle(state)) as
+ | (ViewStyle & FeedbackCornerSource)
+ | undefined;
+ const wash = (
+
+ );
+
+ return decision.overlay === 'behind' ? (
+ {wash}{content}
+ ) : (
+ {content}{wash}
+ );
+ }}
+
+ );
+ },
+);
diff --git a/src/components/MiniPlayer.tsx b/src/components/MiniPlayer.tsx
index d52f286..7322a95 100644
--- a/src/components/MiniPlayer.tsx
+++ b/src/components/MiniPlayer.tsx
@@ -2,7 +2,6 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import {
View,
- Pressable,
type LayoutChangeEvent,
type StyleProp,
type ViewStyle
@@ -28,7 +27,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { motion } from '@/theme/motion';
import { usePlayerStore } from '@/stores/playerStore';
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
@@ -190,7 +189,6 @@ export function MiniPlayer({
}: MiniPlayerProps = {}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const playerOpen = usePlayerOnScreen();
const selectedTarget = usePlaybackTargetStore((s) => s.target);
const track = usePlayerStore((s) => s.currentTrack);
@@ -517,8 +515,8 @@ export function MiniPlayer({
style={[styles.barBlock, { width: miniWidth, height }]}
onLayout={onLayout}
>
-
-
-
-
+
-
-
+
+
{presentation.hasTrack ? (
isDesktop ? (
- usePlayerUiStore.getState().openPlayer()}
accessibilityRole="button"
@@ -705,11 +703,11 @@ export function MiniPlayer({
{displayedMedia.title}
) : null}
-
+
-
-
-
+
-
+
- usePlayerUiStore.getState().openPlayer()}
>
@@ -814,30 +812,30 @@ export function MiniPlayer({
{isDesktop ? (
- setTargetPickerOpen(true)}
style={styles.control}
accessibilityLabel="Choose output device"
>
-
+
) : null}
-
+
-
+
{/* Secondary, so play/pause stays the brightest thing in the
pill. A filled accent disc would read better on a flat
surface, but here it would punch a hole through the live
spectrum drawing behind all of this. */}
-
+
-
+
{presentation.hasTrack ? (
@@ -853,7 +851,7 @@ export function MiniPlayer({
)
) : null}
-
+
)}
-
+
);
}
diff --git a/src/components/ScreenHeader.tsx b/src/components/ScreenHeader.tsx
index caac9a1..8958ad2 100644
--- a/src/components/ScreenHeader.tsx
+++ b/src/components/ScreenHeader.tsx
@@ -2,7 +2,6 @@
import { useCallback, type ReactNode } from 'react';
import {
PixelRatio,
- Pressable,
StyleSheet,
View,
useWindowDimensions,
@@ -21,7 +20,7 @@ import { useShellRailPresent } from '@/navigation/shellRailContext';
import { useShellLayout } from '@/navigation/useShellLayout';
import { MAX_FONT_SCALE, fontSize, variantLineHeight } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import {
SCREEN_HEADER_ACTION_SIZE,
barOpacityAt,
@@ -153,7 +152,6 @@ export function ScreenHeader({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const insets = useSafeAreaInsets();
const { scrollY, layout } = header;
@@ -203,8 +201,8 @@ export function ScreenHeader({
const chevron =
onBack != null ? (
-
-
+
) : null;
const actionCluster =
@@ -391,10 +389,9 @@ export function ScreenHeaderAction({
children: ReactNode;
}) {
const styles = useStyles();
- const ripple = useRipple();
return (
-
{children}
-
+
);
}
diff --git a/src/components/SegmentedControl.tsx b/src/components/SegmentedControl.tsx
index 05c2582..f68b703 100644
--- a/src/components/SegmentedControl.tsx
+++ b/src/components/SegmentedControl.tsx
@@ -1,6 +1,5 @@
import { useEffect } from 'react';
import {
- Pressable,
StyleSheet,
View,
type LayoutChangeEvent
@@ -19,7 +18,7 @@ import {
variantLineHeight,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { motion } from '@/theme/motion';
import { playHaptic } from '@/lib/haptics';
import { useSelectionSlide } from './selectionSlide.ts';
@@ -94,7 +93,6 @@ function SegmentButton({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
// 0 = inactive, 1 = active; drives the label colour cross-fade.
const progress = useSharedValue(focused ? 1 : 0);
@@ -117,8 +115,8 @@ function SegmentButton({
};
return (
-
{label}
-
+
);
}
diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx
index 87773db..3e09405 100644
--- a/src/components/TabBar.tsx
+++ b/src/components/TabBar.tsx
@@ -26,7 +26,6 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
import { motion } from '@/theme/motion';
import { playHaptic } from '@/lib/haptics';
import { useSettingsStore } from '@/stores/settingsStore';
@@ -343,7 +342,6 @@ function TabButton({
}: TabButtonProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
// 0 = inactive, 1 = active. Drives the accent fill, label colour, and bloom.
const progress = useSharedValue(focused ? 1 : 0);
// 0 = at rest, 1 = finger down.
@@ -375,7 +373,7 @@ function TabButton({
// Same transition as the card around it, so a resize is one movement
// rather than a container gliding over children that jumped.
layout={width ? NAV_TRANSITION : undefined}
- android_ripple={ripple.icon(26)}
+
style={[
styles.tab,
rail && styles.railTab,
diff --git a/src/components/appPressableFeedback.test.mts b/src/components/appPressableFeedback.test.mts
new file mode 100644
index 0000000..ef96acb
--- /dev/null
+++ b/src/components/appPressableFeedback.test.mts
@@ -0,0 +1,45 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import {
+ composePressedStyle,
+ resolveFeedbackCorners,
+ resolvePressFeedback,
+} from './appPressableFeedback.ts';
+
+test('maps pressed feedback variants without changing idle or disabled targets', () => {
+ assert.deepEqual(resolvePressFeedback('surface', true, false), { overlay: 'behind', opacity: null });
+ assert.deepEqual(resolvePressFeedback('tile', true, false), { overlay: 'above', opacity: null });
+ assert.deepEqual(resolvePressFeedback('control', true, false), { overlay: null, opacity: 0.74 });
+ assert.deepEqual(resolvePressFeedback('accent', true, false), { overlay: null, opacity: 0.88 });
+ assert.deepEqual(resolvePressFeedback('control', false, false), { overlay: null, opacity: null });
+ assert.deepEqual(resolvePressFeedback('accent', true, true), { overlay: null, opacity: null });
+ assert.deepEqual(resolvePressFeedback('none', true, false), { overlay: null, opacity: null });
+});
+
+test('resolves per-corner radii and lets the explicit radius override them', () => {
+ assert.deepEqual(
+ resolveFeedbackCorners({ borderRadius: 12, borderTopLeftRadius: 4 }),
+ {
+ borderTopLeftRadius: 4,
+ borderTopRightRadius: 12,
+ borderBottomLeftRadius: 12,
+ borderBottomRightRadius: 12,
+ },
+ );
+ assert.deepEqual(resolveFeedbackCorners({ borderRadius: 12 }, 8), {
+ borderTopLeftRadius: 8,
+ borderTopRightRadius: 8,
+ borderBottomLeftRadius: 8,
+ borderBottomRightRadius: 8,
+ });
+});
+
+test('preserves caller styles while appending pressed opacity', () => {
+ const callerStyle = [{ flex: 1 }, { opacity: 0.5 }];
+ const decision = resolvePressFeedback('control', true, false);
+ assert.deepEqual(composePressedStyle(callerStyle, decision), [callerStyle, { opacity: 0.74 }]);
+ assert.equal(
+ composePressedStyle(callerStyle, resolvePressFeedback('surface', false, false)),
+ callerStyle,
+ );
+});
diff --git a/src/components/appPressableFeedback.ts b/src/components/appPressableFeedback.ts
new file mode 100644
index 0000000..1012297
--- /dev/null
+++ b/src/components/appPressableFeedback.ts
@@ -0,0 +1,55 @@
+export type AppPressFeedback = 'surface' | 'tile' | 'control' | 'accent' | 'none';
+
+export interface PressFeedbackDecision {
+ overlay: 'behind' | 'above' | null;
+ opacity: number | null;
+}
+
+export interface FeedbackCornerSource {
+ borderRadius?: number;
+ borderTopLeftRadius?: number;
+ borderTopRightRadius?: number;
+ borderBottomLeftRadius?: number;
+ borderBottomRightRadius?: number;
+}
+
+export interface FeedbackCorners {
+ borderTopLeftRadius?: number;
+ borderTopRightRadius?: number;
+ borderBottomLeftRadius?: number;
+ borderBottomRightRadius?: number;
+}
+
+const IDLE_FEEDBACK: PressFeedbackDecision = { overlay: null, opacity: null };
+
+export function resolvePressFeedback(
+ feedback: AppPressFeedback,
+ pressed: boolean,
+ disabled: boolean,
+): PressFeedbackDecision {
+ if (!pressed || disabled || feedback === 'none') return IDLE_FEEDBACK;
+ if (feedback === 'surface') return { overlay: 'behind', opacity: null };
+ if (feedback === 'tile') return { overlay: 'above', opacity: null };
+ if (feedback === 'control') return { overlay: null, opacity: 0.74 };
+ return { overlay: null, opacity: 0.88 };
+}
+
+export function resolveFeedbackCorners(
+ style: FeedbackCornerSource | undefined,
+ override?: number,
+): FeedbackCorners {
+ const fallback = override ?? style?.borderRadius;
+ return {
+ borderTopLeftRadius: override ?? style?.borderTopLeftRadius ?? fallback,
+ borderTopRightRadius: override ?? style?.borderTopRightRadius ?? fallback,
+ borderBottomLeftRadius: override ?? style?.borderBottomLeftRadius ?? fallback,
+ borderBottomRightRadius: override ?? style?.borderBottomRightRadius ?? fallback,
+ };
+}
+
+export function composePressedStyle(
+ style: T,
+ decision: PressFeedbackDecision,
+): T | [T, { opacity: number }] {
+ return decision.opacity == null ? style : [style, { opacity: decision.opacity }];
+}
diff --git a/src/components/dialogs/AppDialog.tsx b/src/components/dialogs/AppDialog.tsx
index 635fca4..586101c 100644
--- a/src/components/dialogs/AppDialog.tsx
+++ b/src/components/dialogs/AppDialog.tsx
@@ -1,7 +1,6 @@
import { useSyncExternalStore } from 'react';
import {
Modal,
- Pressable,
ScrollView,
StyleSheet,
View,
@@ -9,7 +8,7 @@ import {
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import {
dismissActiveDialog,
EMPTY_DIALOG_QUEUE,
@@ -65,7 +64,6 @@ export function AppDialogHost() {
const active = queue.active;
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
if (!active) return null;
@@ -119,9 +117,9 @@ export function AppDialogHost() {
? colors.textSecondary
: colors.accent;
return (
- [
styles.action,
pressed ? styles.actionPressed : null,
@@ -133,7 +131,7 @@ export function AppDialogHost() {
{action.label}
-
+
);
})}
diff --git a/src/components/eq/BandConsole.tsx b/src/components/eq/BandConsole.tsx
index 98708dd..1851c1f 100644
--- a/src/components/eq/BandConsole.tsx
+++ b/src/components/eq/BandConsole.tsx
@@ -1,10 +1,10 @@
-import { Pressable, ScrollView, StyleSheet, View } from 'react-native';
+import { ScrollView, StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { HapticSwitch } from '@/components/HapticSwitch';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import {
EQ_MAX_GAIN_DB,
isPassEQBandType,
@@ -68,7 +68,6 @@ export function BandConsole({
}: BandConsoleProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
- onEditType(band.id)}
@@ -116,7 +115,7 @@ export function BandConsole({
{BAND_TYPE_LABEL[band.type]}
-
+
{/*
A pass filter has no gain to set. Keeping the rail in place rather
@@ -156,13 +155,13 @@ export function BandConsole({
value={isShelf ? '—' : band.Q.toFixed(2)}
onPress={isShelf ? undefined : () => onEditValue(band.id, 'Q')}
/>
-
+
);
})}
{canAdd ? (
-
-
+
) : null}
);
@@ -195,7 +194,6 @@ function Readout({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const body = (
<>
{body};
}
return (
-
{body}
-
+
);
}
diff --git a/src/components/eq/BandDetailPanel.tsx b/src/components/eq/BandDetailPanel.tsx
index 317bb2f..1c71531 100644
--- a/src/components/eq/BandDetailPanel.tsx
+++ b/src/components/eq/BandDetailPanel.tsx
@@ -1,5 +1,4 @@
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -11,7 +10,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { EQBand } from '@/types/audio';
import {
EQ_MAX_FREQUENCY,
@@ -44,7 +43,6 @@ export type EQEditableValue = 'frequency' | 'gain' | 'Q';
/** "Band N" + type dropdown + On toggle + audible parameter sliders. */
export function BandDetailPanel({ band, bandNumber, onUpdate, onEditType, onEditValue }: BandDetailPanelProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
if (!band) {
return (
@@ -63,12 +61,12 @@ export function BandDetailPanel({ band, bandNumber, onUpdate, onEditType, onEdit
Band {bandNumber}
-
+
{BAND_TYPE_LABEL[band.type]}
-
+
{band.enabled ? 'On' : 'Off'}
{
const isActive = band.id === activeBandId;
return (
- onSelect(band.id)}
style={[styles.cell, isActive && styles.cellActive]}
@@ -54,13 +52,13 @@ export function BandStrip({ bands, activeBandId, canAdd, onSelect, onAdd }: Band
>
{formatGain(band.gain)}
-
+
);
})}
{canAdd ? (
-
+
-
+
) : null}
);
diff --git a/src/components/eq/EQPresetNameSheet.tsx b/src/components/eq/EQPresetNameSheet.tsx
index 1e06577..7e21c95 100644
--- a/src/components/eq/EQPresetNameSheet.tsx
+++ b/src/components/eq/EQPresetNameSheet.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -12,7 +11,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { EqSheet } from './EqSheet';
interface EQPresetNameSheetProps {
@@ -29,7 +28,6 @@ export function EQPresetNameSheet({
onClose,
}: EQPresetNameSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [name, setName] = useState(defaultName);
const trimmed = name.trim();
@@ -58,16 +56,16 @@ export function EQPresetNameSheet({
onSubmitEditing={submit}
/>
-
+
Cancel
-
-
+
+
{actionLabel}
-
+
);
diff --git a/src/components/eq/EQPresetPreviewSheet.tsx b/src/components/eq/EQPresetPreviewSheet.tsx
index 0ef3fd5..6510476 100644
--- a/src/components/eq/EQPresetPreviewSheet.tsx
+++ b/src/components/eq/EQPresetPreviewSheet.tsx
@@ -1,5 +1,4 @@
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -9,7 +8,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { EQPreset } from '@/types/audio';
import { EqSheet } from './EqSheet';
import { formatGain } from './format';
@@ -30,7 +29,6 @@ export function EQPresetPreviewSheet({
onClose,
}: EQPresetPreviewSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const enabledBands = preset.bands.filter((band) => band.enabled).length;
const modeLabel = preset.mode === 'graphic' ? 'Graphic' : 'Parametric';
@@ -70,12 +68,12 @@ export function EQPresetPreviewSheet({
-
+
Cancel
-
-
+ {
onConfirm();
@@ -85,7 +83,7 @@ export function EQPresetPreviewSheet({
{confirmLabel}
-
+
);
diff --git a/src/components/eq/EQPresetQrSheet.tsx b/src/components/eq/EQPresetQrSheet.tsx
index 9513588..8244660 100644
--- a/src/components/eq/EQPresetQrSheet.tsx
+++ b/src/components/eq/EQPresetQrSheet.tsx
@@ -1,5 +1,4 @@
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -10,7 +9,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { EqSheet } from './EqSheet';
interface EQPresetQrSheetProps {
@@ -21,7 +20,6 @@ interface EQPresetQrSheetProps {
export function EQPresetQrSheet({ presetName, value, onClose }: EQPresetQrSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
@@ -35,11 +33,11 @@ export function EQPresetQrSheet({ presetName, value, onClose }: EQPresetQrSheetP
{presetName}
-
+
Done
-
+
);
}
diff --git a/src/components/eq/EQSlider.tsx b/src/components/eq/EQSlider.tsx
index 332f208..293e951 100644
--- a/src/components/eq/EQSlider.tsx
+++ b/src/components/eq/EQSlider.tsx
@@ -1,6 +1,5 @@
import { useRef, useState } from 'react';
import {
- Pressable,
View,
StyleSheet,
type GestureResponderEvent,
@@ -12,7 +11,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
const THUMB = 16;
@@ -44,7 +43,6 @@ export function EQSlider({
onValuePress,
}: EQSliderProps) {
const styles = useStyles();
- const ripple = useRipple();
const [width, setWidth] = useState(0);
const [active, setActive] = useState(false);
const widthRef = useRef(0);
@@ -120,10 +118,10 @@ export function EQSlider({
/>
{onValuePress && !disabled ? (
- [styles.valueButton, pressed && styles.valueButtonPressed]}
onPress={onValuePress}
accessibilityRole="button"
@@ -132,7 +130,7 @@ export function EQSlider({
{format(value)}
-
+
) : (
{format(value)}
diff --git a/src/components/eq/EQValueEditSheet.tsx b/src/components/eq/EQValueEditSheet.tsx
index ea8e5f0..c1b066a 100644
--- a/src/components/eq/EQValueEditSheet.tsx
+++ b/src/components/eq/EQValueEditSheet.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View,
type KeyboardTypeOptions
@@ -13,7 +12,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { EqSheet } from './EqSheet';
interface EQValueEditSheetProps {
@@ -41,7 +40,6 @@ export function EQValueEditSheet({
onClose,
}: EQValueEditSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [value, setValue] = useState(initialValue);
const trimmed = value.trim();
@@ -82,12 +80,12 @@ export function EQValueEditSheet({
{valid || trimmed.length === 0 ? rangeLabel : 'Enter a valid number'}
-
+
Cancel
-
-
+
Apply
-
+
);
diff --git a/src/components/eq/PresetDeviceAssignmentSheet.tsx b/src/components/eq/PresetDeviceAssignmentSheet.tsx
index b249d72..510e12d 100644
--- a/src/components/eq/PresetDeviceAssignmentSheet.tsx
+++ b/src/components/eq/PresetDeviceAssignmentSheet.tsx
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { EqSheet } from '@/components/eq/EqSheet';
@@ -7,7 +7,7 @@ import type { KnownEQOutputDevice } from '@/audio/eqDevicePresets';
import type { EQPreset } from '@/types/audio';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
interface PresetDeviceAssignmentSheetProps {
@@ -49,7 +49,6 @@ export function PresetDeviceAssignmentSheet({
}: PresetDeviceAssignmentSheetProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const [selected, setSelected] = useState>(
() => new Set(devices.filter((device) => assignments[device.key] === preset.id).map((device) => device.key))
);
@@ -101,9 +100,9 @@ export function PresetDeviceAssignmentSheet({
? `Currently assigned to ${assignedPresetName}`
: kindLabel(device);
return (
- toggleDevice(device.key)}
@@ -131,18 +130,18 @@ export function PresetDeviceAssignmentSheet({
size={22}
color={checked ? colors.accent : colors.textTertiary}
/>
-
+
);
})}
)}
-
+
Cancel
-
-
+ {
onSave([...selected]);
@@ -150,7 +149,7 @@ export function PresetDeviceAssignmentSheet({
}}
>
Save assignments
-
+
);
diff --git a/src/components/eq/PresetSheet.tsx b/src/components/eq/PresetSheet.tsx
index a2c086d..fd6d8be 100644
--- a/src/components/eq/PresetSheet.tsx
+++ b/src/components/eq/PresetSheet.tsx
@@ -1,9 +1,9 @@
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import type { KnownEQOutputDevice } from '@/audio/eqDevicePresets';
import type { EQPreset } from '@/types/audio';
import {
@@ -45,7 +45,6 @@ export function PresetSheet({
onClose,
}: PresetSheetProps) {
const colors = useColors();
- const ripple = useRipple();
const builtIn = presets.filter((p) => !p.isCustom);
const custom = presets.filter((p) => p.isCustom);
const devicesByKey = new Map(knownDevices.map((device) => [device.key, device]));
@@ -72,8 +71,8 @@ export function PresetSheet({
}}
trailing={
- onAssign(preset)}
@@ -84,10 +83,10 @@ export function PresetSheet({
Assign
-
+
{preset.isCustom ? (
- onDelete(preset)}
@@ -95,7 +94,7 @@ export function PresetSheet({
accessibilityLabel={`Delete preset ${preset.name}`}
>
-
+
) : null}
}
diff --git a/src/components/eq/SavePresetSheet.tsx b/src/components/eq/SavePresetSheet.tsx
index f44dcf2..157715c 100644
--- a/src/components/eq/SavePresetSheet.tsx
+++ b/src/components/eq/SavePresetSheet.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -13,7 +12,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { EqSheet } from './EqSheet';
interface SavePresetSheetProps {
@@ -31,7 +30,6 @@ export function SavePresetSheet({
onClose,
}: SavePresetSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [name, setName] = useState(defaultName);
const [assignToCurrentDevice, setAssignToCurrentDevice] = useState(false);
@@ -76,12 +74,12 @@ export function SavePresetSheet({
) : null}
-
+
Cancel
-
-
+ {
@@ -92,7 +90,7 @@ export function SavePresetSheet({
Save
-
+
);
diff --git a/src/components/library/AlbumGridItem.tsx b/src/components/library/AlbumGridItem.tsx
index e6aff20..4743216 100644
--- a/src/components/library/AlbumGridItem.tsx
+++ b/src/components/library/AlbumGridItem.tsx
@@ -1,6 +1,5 @@
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -11,16 +10,15 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { albumArtworkSource } from '@/library/artwork';
import type { Album } from '@/types/library';
export function AlbumGridItem({ album, onPress }: { album: Album; onPress: () => void }) {
const styles = useStyles();
- const ripple = useRipple();
const artUri = albumArtworkSource(album);
return (
-
+
{artUri ? (
{album.artist}
-
+
);
}
diff --git a/src/components/library/AlbumRow.tsx b/src/components/library/AlbumRow.tsx
index 5d134a9..76118cf 100644
--- a/src/components/library/AlbumRow.tsx
+++ b/src/components/library/AlbumRow.tsx
@@ -1,6 +1,5 @@
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -11,7 +10,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { albumArtworkSource } from '@/library/artwork';
import type { Album } from '@/types/library';
@@ -19,10 +18,9 @@ import type { Album } from '@/types/library';
export function AlbumRow({ album, onPress }: { album: Album; onPress: () => void }) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const artUri = albumArtworkSource(album);
return (
-
+
{artUri ? (
void
-
+
);
}
diff --git a/src/components/library/ArtistGridItem.tsx b/src/components/library/ArtistGridItem.tsx
index d5d7d4f..898eed5 100644
--- a/src/components/library/ArtistGridItem.tsx
+++ b/src/components/library/ArtistGridItem.tsx
@@ -1,6 +1,5 @@
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -11,7 +10,7 @@ import {
radius,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { artworkUri } from '@/library/artwork';
import type { Artist } from '@/types/library';
@@ -19,7 +18,6 @@ import type { Artist } from '@/types/library';
export function ArtistGridItem({ artist, onPress }: { artist: Artist; onPress: () => void }) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const useMosaic = artist.artwork_hashes.length >= 4;
const hashes = useMosaic ? artist.artwork_hashes.slice(0, 4) : artist.artwork_hashes.slice(0, 1);
@@ -27,7 +25,7 @@ export function ArtistGridItem({ artist, onPress }: { artist: Artist; onPress: (
const tracks = `${artist.track_count} ${artist.track_count === 1 ? 'track' : 'tracks'}`;
return (
-
+
{hashes.length === 0 ? (
@@ -58,7 +56,7 @@ export function ArtistGridItem({ artist, onPress }: { artist: Artist; onPress: (
{albums} · {tracks}
-
+
);
}
diff --git a/src/components/library/ArtistImageSearchSheet.tsx b/src/components/library/ArtistImageSearchSheet.tsx
index 2107fb1..667ba87 100644
--- a/src/components/library/ArtistImageSearchSheet.tsx
+++ b/src/components/library/ArtistImageSearchSheet.tsx
@@ -13,7 +13,7 @@ import { AppSheet, AppSheetTitle } from '@/components/sheets/AppSheet';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { searchArtistImageCandidates } from '@/library/artistImageLookup';
import type { DeezerArtistCandidate } from '@/types/artistImages';
@@ -28,7 +28,6 @@ export function ArtistImageSearchSheet({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const [query, setQuery] = useState(artistName);
const [candidates, setCandidates] = useState([]);
const [loading, setLoading] = useState(false);
@@ -101,8 +100,8 @@ export function ArtistImageSearchSheet({
style={styles.input}
accessibilityLabel="Deezer artist search"
/>
- void search()}
accessibilityRole="button"
@@ -113,7 +112,7 @@ export function ArtistImageSearchSheet({
) : (
)}
-
+
{error ? (
@@ -127,9 +126,9 @@ export function ArtistImageSearchSheet({
{candidates.map((candidate) => (
- void choose(candidate)}
accessibilityRole="button"
@@ -166,7 +165,7 @@ export function ArtistImageSearchSheet({
) : (
)}
-
+
))}
diff --git a/src/components/library/ArtistImageSweepStatus.tsx b/src/components/library/ArtistImageSweepStatus.tsx
index b683286..15ba0b2 100644
--- a/src/components/library/ArtistImageSweepStatus.tsx
+++ b/src/components/library/ArtistImageSweepStatus.tsx
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
-import { ActivityIndicator, Pressable, StyleSheet, View } from 'react-native';
+import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useArtistImageStore } from '@/stores/artistImageStore';
import { requeueMissingArtistImages } from '@/library/artistImageLookup';
@@ -19,7 +19,6 @@ const n = (value: number) => value.toLocaleString();
export function ArtistImageSweepStatus({ enabled }: { enabled: boolean }) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const running = useArtistImageStore((s) => s.running);
const processed = useArtistImageStore((s) => s.processed);
const total = useArtistImageStore((s) => s.total);
@@ -73,8 +72,8 @@ export function ArtistImageSweepStatus({ enabled }: { enabled: boolean }) {
{missing === 1 ? '1 artist has no image' : `${n(missing)} artists have no image`}
- void retry()}
@@ -89,7 +88,7 @@ export function ArtistImageSweepStatus({ enabled }: { enabled: boolean }) {
Look for missing images
-
+
);
}
diff --git a/src/components/library/ArtistRow.tsx b/src/components/library/ArtistRow.tsx
index 56f20fa..f51ba5a 100644
--- a/src/components/library/ArtistRow.tsx
+++ b/src/components/library/ArtistRow.tsx
@@ -1,6 +1,5 @@
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -11,18 +10,17 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { artworkUri } from '@/library/artwork';
import type { Artist } from '@/types/library';
export function ArtistRow({ artist, onPress }: { artist: Artist; onPress: () => void }) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const albums = `${artist.album_count} ${artist.album_count === 1 ? 'album' : 'albums'}`;
const tracks = `${artist.track_count} ${artist.track_count === 1 ? 'track' : 'tracks'}`;
return (
-
+
{artist.artwork_hash ? (
-
+
);
}
diff --git a/src/components/library/CollapsingDetail.tsx b/src/components/library/CollapsingDetail.tsx
index 2106341..e1fb43f 100644
--- a/src/components/library/CollapsingDetail.tsx
+++ b/src/components/library/CollapsingDetail.tsx
@@ -7,7 +7,6 @@ import {
type ReactNode
} from 'react';
import {
- Pressable,
StyleSheet,
View,
useWindowDimensions,
@@ -37,7 +36,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import {
DETAIL_ART_COLLAPSED,
DETAIL_BAR_H,
@@ -202,7 +201,6 @@ export function CollapsingHeader({
onHeroBlockLayout: (e: LayoutChangeEvent) => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const insets = useSafeAreaInsets();
const { width: W, height: H } = useWindowDimensions();
@@ -362,7 +360,7 @@ export function CollapsingHeader({
{heroMeta}
{heroExtra}
-
Play
-
-
+
Shuffle
-
+
-
-
+
-
+
-
-
+
+
-
+
{onMore ? (
-
-
+
) : null}
{/* Rendered last so the large art sits on top of the header text until it tucks away. */}
diff --git a/src/components/library/EmptyLibrary.tsx b/src/components/library/EmptyLibrary.tsx
index 5936e6c..02bdbdf 100644
--- a/src/components/library/EmptyLibrary.tsx
+++ b/src/components/library/EmptyLibrary.tsx
@@ -1,6 +1,5 @@
import {
- View,
- Pressable
+ View
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
@@ -10,12 +9,11 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useLibraryStore } from '@/stores/libraryStore';
export function EmptyLibrary() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const router = useRouter();
const recoveryNotice = useLibraryStore((state) => state.recoveryNotice);
@@ -50,8 +48,8 @@ export function EmptyLibrary() {
: recoveryNotice ??
'Pick a folder on this device and Astra will scan it into your library.'}
- router.push(fatal ? '/settings/troubleshooting' : '/settings')}
accessibilityRole="button"
@@ -60,7 +58,7 @@ export function EmptyLibrary() {
{fatal ? 'Troubleshooting' : 'Folder settings'}
-
+
);
}
diff --git a/src/components/library/FoldersView.tsx b/src/components/library/FoldersView.tsx
index a338f78..d6967d0 100644
--- a/src/components/library/FoldersView.tsx
+++ b/src/components/library/FoldersView.tsx
@@ -28,7 +28,7 @@ import { formatDuration } from '@/lib/format';
import { playHaptic } from '@/lib/haptics';
import { spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { usePlayerStore } from '@/stores/playerStore';
import type { DbTrack } from '@/types/library';
import { useSceneBottomInset } from '@/navigation/useShellLayout';
@@ -81,15 +81,14 @@ function FolderNodeRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const { node } = state;
const stop = (callback: () => void) => (event: GestureResponderEvent) => {
event.stopPropagation();
callback();
};
return (
-
{node.totalTrackCount}
-
-
-
+
-
-
+
+
);
}
@@ -157,10 +156,9 @@ function FolderTrackRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
- {
@@ -195,8 +193,8 @@ function FolderTrackRow({
{track.artist}
{formatDuration(track.duration)}
- {
@@ -208,8 +206,8 @@ function FolderTrackRow({
accessibilityLabel={`More actions for ${track.title}`}
>
-
-
+
+
);
}
diff --git a/src/components/library/LibraryContextBar.tsx b/src/components/library/LibraryContextBar.tsx
index 5b959b3..9d094db 100644
--- a/src/components/library/LibraryContextBar.tsx
+++ b/src/components/library/LibraryContextBar.tsx
@@ -1,7 +1,6 @@
/* eslint-disable react-hooks/immutability -- Reanimated shared values are mutable gesture state. */
import { useEffect, useMemo } from 'react';
import {
- Pressable,
StyleSheet,
View,
useWindowDimensions,
@@ -20,7 +19,7 @@ import { useSelectionSlide } from '@/components/selectionSlide';
import { radius, spacing } from '@/theme';
import { motion } from '@/theme/motion';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
import {
LIBRARY_CONTEXT_BAR_HEIGHT,
@@ -79,7 +78,6 @@ export function LibraryContextBar({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const { width, fontScale } = useWindowDimensions();
const showActiveLabel = libraryDockShowsActiveLabel(width, fontScale);
const modeIndex = LIBRARY_VIEW_MODES.findIndex((entry) => entry.key === mode);
@@ -256,9 +254,9 @@ export function LibraryContextBar({
{LIBRARY_VIEW_MODES.map((entry) => {
const selected = entry.key === mode;
return (
-
) : null}
-
+
);
})}
@@ -329,10 +327,9 @@ function ContextAction({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
-
-
+
);
}
diff --git a/src/components/library/PlaylistRow.tsx b/src/components/library/PlaylistRow.tsx
index 77f750c..6652774 100644
--- a/src/components/library/PlaylistRow.tsx
+++ b/src/components/library/PlaylistRow.tsx
@@ -1,6 +1,5 @@
import {
View,
- Pressable,
StyleSheet
} from 'react-native';
import { Image } from 'expo-image';
@@ -11,7 +10,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { artworkUri } from '@/library/artwork';
import { playHaptic } from '@/lib/haptics';
@@ -41,10 +40,9 @@ export function PlaylistRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
-
-
+
);
}
diff --git a/src/components/library/ScanNotificationPermissionCard.tsx b/src/components/library/ScanNotificationPermissionCard.tsx
index 5ef3cd3..1c0cd4d 100644
--- a/src/components/library/ScanNotificationPermissionCard.tsx
+++ b/src/components/library/ScanNotificationPermissionCard.tsx
@@ -1,6 +1,5 @@
import {
ActivityIndicator,
- Pressable,
StyleSheet,
View,
type StyleProp,
@@ -10,7 +9,7 @@ import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useScanNotificationPermission } from '@/library/useScanNotificationPermission';
/**
@@ -31,7 +30,6 @@ export function ScanNotificationPermissionCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const { state, granted, denied, working, resolve } = useScanNotificationPermission();
// Also hidden while the first check is in flight, so the row never appears
@@ -53,8 +51,8 @@ export function ScanNotificationPermissionCard({
-
{denied ? 'Open Settings' : 'Allow scan notifications'}
-
+
{denied ? (
diff --git a/src/components/library/ScanProgress.tsx b/src/components/library/ScanProgress.tsx
index f230b3d..05e72e0 100644
--- a/src/components/library/ScanProgress.tsx
+++ b/src/components/library/ScanProgress.tsx
@@ -1,7 +1,7 @@
-import { Pressable, View } from 'react-native';
+import { View } from 'react-native';
import { Text } from '@/components/Text';
import { spacing } from '@/theme';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { createThemedStyles, useColors } from '@/theme/themed';
import { useLibraryStore } from '@/stores/libraryStore';
@@ -9,7 +9,6 @@ import { useLibraryStore } from '@/stores/libraryStore';
export function ScanProgress() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const isScanning = useLibraryStore((s) => s.isScanning);
const isCancelling = useLibraryStore((s) => s.isCancelling);
const progress = useLibraryStore((s) => s.scanProgress);
@@ -42,8 +41,8 @@ export function ScanProgress() {
>
{label}
-
{isCancelling ? 'Cancelling…' : 'Cancel'}
-
+
{label}
-
+
);
}
diff --git a/src/components/library/TrackRow.tsx b/src/components/library/TrackRow.tsx
index 71402be..7bc68c5 100644
--- a/src/components/library/TrackRow.tsx
+++ b/src/components/library/TrackRow.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react';
import {
View,
- Pressable,
StyleSheet,
type GestureResponderEvent
} from 'react-native';
@@ -17,7 +16,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { formatDuration } from '@/lib/format';
import { playHaptic } from '@/lib/haptics';
import { trackArtworkThumbSource } from '@/library/artwork';
@@ -68,7 +67,6 @@ export function TrackRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
// Key the artwork by hash (local) or identity path (remote) so the error fallback
// and FlashList recycling work for both.
const artKey = track.source_type !== 'local' ? track.path : track.artwork_hash;
@@ -94,8 +92,8 @@ export function TrackRow({
};
const row = (
-
{onOpenActions && !selectionMode ? (
-
-
+
) : null}
-
+
);
if (!swipeToQueue || selectionMode) return row;
diff --git a/src/components/listening/ListeningPreviewCard.tsx b/src/components/listening/ListeningPreviewCard.tsx
index 21068f6..0ffdeb2 100644
--- a/src/components/listening/ListeningPreviewCard.tsx
+++ b/src/components/listening/ListeningPreviewCard.tsx
@@ -1,13 +1,13 @@
import { Image } from 'expo-image';
import { Ionicons } from '@expo/vector-icons';
-import { Pressable, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
+import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
import { AstraLogo } from '@/components/AstraLogo';
import { Text } from '@/components/Text';
import { listeningArtworkSource } from '@/library/artwork';
import { formatListeningTime } from '@/listeningStats/format';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { ListeningStatsDashboard } from '@/types/listeningStats';
/**
@@ -30,7 +30,6 @@ export function ListeningPreviewCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
if (!hasListeningPreview(dashboard) || !dashboard) return null;
const topTrack = dashboard.topTracks[0] ?? null;
@@ -38,9 +37,9 @@ export function ListeningPreviewCard({
const paused = !dashboard.status.enabled;
return (
-
)}
-
+
);
}
diff --git a/src/components/lyrics/LyricsBand.tsx b/src/components/lyrics/LyricsBand.tsx
index c6c341d..c45fef4 100644
--- a/src/components/lyrics/LyricsBand.tsx
+++ b/src/components/lyrics/LyricsBand.tsx
@@ -7,12 +7,12 @@
// static scroll; loading/not-found/error states get a centered message.
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { ActivityIndicator, Pressable, ScrollView, View, type LayoutChangeEvent } from 'react-native';
+import { ActivityIndicator, ScrollView, View, type LayoutChangeEvent } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { useSmoothPlaybackTime } from '@/audio/useSmoothPlaybackTime';
import { useLyricsStore } from '@/stores/lyricsStore';
import { useLyricsSettingsStore } from '@/stores/lyricsSettingsStore';
@@ -84,7 +84,6 @@ export function LyricsBand({
}: LyricsBandProps) {
const { fixedSize, maxSize, hPadding } = SURFACE[surface];
const colors = useColors();
- const ripple = useRipple();
const entry = useLyricsStore((s) => s.byPath[track.path]);
const loadForTrack = useLyricsStore((s) => s.loadForTrack);
const wordTimingEnabled = useLyricsSettingsStore((s) => s.wordTimingEnabled);
@@ -201,8 +200,8 @@ export function LyricsBand({
{emptyState.message}
{emptyState.retryable ? (
- void loadForTrack(track, { force: true })}
accessibilityRole="button"
@@ -232,7 +231,7 @@ export function LyricsBand({
{isLoading ? 'Retrying…' : 'Retry'}
-
+
) : null}
);
@@ -310,7 +309,7 @@ export function LyricsBand({
{followPaused ? (
-
Recenter
-
+
) : null}
);
diff --git a/src/components/lyrics/LyricsLine.tsx b/src/components/lyrics/LyricsLine.tsx
index 9ea276d..baf97e5 100644
--- a/src/components/lyrics/LyricsLine.tsx
+++ b/src/components/lyrics/LyricsLine.tsx
@@ -1,9 +1,9 @@
import { memo, useEffect, useMemo, useState } from 'react';
-import { Pressable, View, type LayoutChangeEvent } from 'react-native';
+import { View, type LayoutChangeEvent } from 'react-native';
import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
import { Text } from '@/components/Text';
import { useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { getPreferredLyricsTranslation, resolveLyricsWordTiming } from '@/lyrics/presentation';
import type { LyricsFurigana, LyricsLine as LyricsLineData, LyricsWord } from '@/lyrics/types';
@@ -183,7 +183,6 @@ function LyricsLineComponent({
onLayout,
}: LyricsLineProps) {
const colors = useColors();
- const ripple = useRipple();
const target = TIER[tier];
const size = baseSize;
const lineHeight = Math.round(size * 1.2);
@@ -218,7 +217,7 @@ function LyricsLineComponent({
return (
-
+
{voiceLabelsEnabled && line.voice?.trim() ? (
@@ -268,7 +267,7 @@ function LyricsLineComponent({
) : null}
-
+
);
}
diff --git a/src/components/lyrics/LyricsView.tsx b/src/components/lyrics/LyricsView.tsx
index 44833b3..c690aec 100644
--- a/src/components/lyrics/LyricsView.tsx
+++ b/src/components/lyrics/LyricsView.tsx
@@ -6,7 +6,7 @@
// art view; swipe-down still closes the player.
import { Image } from 'expo-image';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { MarqueeText } from '@/components/MarqueeText';
@@ -20,7 +20,7 @@ import {
import { LyricsBand } from './LyricsBand';
import { spacing, radius } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { usePlayerStore } from '@/stores/playerStore';
import { useLyricsStore } from '@/stores/lyricsStore';
import { getLyricsPayloadSourceLabel } from '@/lyrics/presentation';
@@ -57,7 +57,6 @@ export function LyricsView({
onDismiss,
}: LyricsViewProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
// Lyrics mode is phone-target only, so progress comes straight from the
// player store — the 2Hz tick re-renders this takeover, not the whole screen.
@@ -71,9 +70,9 @@ export function LyricsView({
return (
-
+
-
+
-
-
+
-
+
-
+
- (initialPolicy);
const [saving, setSaving] = useState(false);
@@ -72,8 +71,8 @@ function ArtistImageDisclosureContent({
Ethernet is included in Wi-Fi mode. You can change this later in Settings › Library,
and manual searches work while automatic downloads are off.
- void continueSetup()}
disabled={saving}
@@ -84,7 +83,7 @@ function ArtistImageDisclosureContent({
) : (
Continue
)}
-
+
diff --git a/src/components/onboarding/ArtistImageStep.tsx b/src/components/onboarding/ArtistImageStep.tsx
index d8e6340..f86fdd8 100644
--- a/src/components/onboarding/ArtistImageStep.tsx
+++ b/src/components/onboarding/ArtistImageStep.tsx
@@ -1,5 +1,5 @@
import { useEffect, type ComponentProps, type ReactNode } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
@@ -11,7 +11,7 @@ import { StepHeader } from '@/components/onboarding/StepHeader';
import { radius, spacing } from '@/theme';
import { motion } from '@/theme/motion';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
import { useSettingsStore } from '@/stores/settingsStore';
import type { ArtistImageAutoPolicy } from '@/types/artistImages';
@@ -175,10 +175,9 @@ function PolicyCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
-
)}
-
+
);
}
diff --git a/src/components/onboarding/NotificationStep.tsx b/src/components/onboarding/NotificationStep.tsx
index af30001..6747e6b 100644
--- a/src/components/onboarding/NotificationStep.tsx
+++ b/src/components/onboarding/NotificationStep.tsx
@@ -1,10 +1,10 @@
-import { ActivityIndicator, Pressable, StyleSheet, View } from 'react-native';
+import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { StepHeader } from '@/components/onboarding/StepHeader';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { useLibraryStore } from '@/stores/libraryStore';
import type { ScanNotificationPermission } from '@/library/useScanNotificationPermission';
@@ -22,7 +22,6 @@ export function NotificationStep({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const isScanning = useLibraryStore((s) => s.isScanning);
const { state, granted, denied, working, resolve } = permission;
@@ -52,8 +51,8 @@ export function NotificationStep({
) : (
-
{denied ? 'Open Android settings' : 'Allow notifications'}
-
+
)}
diff --git a/src/components/onboarding/OnboardingFlow.tsx b/src/components/onboarding/OnboardingFlow.tsx
index 03df019..cd7b1ba 100644
--- a/src/components/onboarding/OnboardingFlow.tsx
+++ b/src/components/onboarding/OnboardingFlow.tsx
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import {
ActivityIndicator,
- Pressable,
ScrollView,
StyleSheet,
View,
@@ -30,7 +29,7 @@ import { formatFolderCount, formatTrackCount } from '@/components/settings/Setti
import { radius, spacing } from '@/theme';
import { motion } from '@/theme/motion';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
import type { BaseThemeId } from '@/theme/resolve';
import { useScanNotificationPermission } from '@/library/useScanNotificationPermission';
@@ -77,7 +76,6 @@ const WIZARD_THEME_OPTIONS: { id: BaseThemeId; title: string }[] = [
*/
export function OnboardingFlow({ onDone }: { onDone: () => void }) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const insets = useSafeAreaInsets();
const { width, height } = useWindowDimensions();
@@ -173,7 +171,7 @@ export function OnboardingFlow({ onDone }: { onDone: () => void }) {
{canGoBack ? (
- void }) {
Back
-
+
) : null}
- void }) {
{primaryLabel}
-
+
@@ -223,7 +221,6 @@ function WelcomeStep() {
function LibraryStep() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const folders = useLibraryStore((s) => s.folders);
const totalTrackCount = useLibraryStore((s) => s.totalTrackCount);
@@ -238,7 +235,7 @@ function LibraryStep() {
subtitle="Point Astra at the folders where your music lives. It scans them into your library — files on disk are never modified."
/>
- void addFolder()}
@@ -248,7 +245,7 @@ function LibraryStep() {
{folders.length > 0 ? 'Add another folder' : 'Choose music folder'}
-
+
@@ -275,7 +272,6 @@ function LibraryStep() {
function ThemeStep() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const baseTheme = useThemeStore((s) => s.baseTheme);
const materialYouAvailable = useThemeStore((s) => s.materialYouAvailable);
@@ -298,7 +294,7 @@ function ThemeStep() {
{options.map((option) => {
const selected = option.id === baseTheme;
return (
- {
if (selected) return;
@@ -315,7 +311,7 @@ function ThemeStep() {
>
{option.title}
-
+
);
})}
@@ -375,7 +371,6 @@ function DoneStep() {
function ScanBanner() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const isScanning = useLibraryStore((s) => s.isScanning);
const isCancelling = useLibraryStore((s) => s.isCancelling);
const progress = useLibraryStore((s) => s.scanProgress);
@@ -402,8 +397,8 @@ function ScanBanner() {
? 'Cancelling library scan…'
: `Scanning your library${detail ? ` · ${detail}` : '…'}`}
-
{isCancelling ? 'Cancelling…' : 'Cancel'}
-
+
);
}
diff --git a/src/components/player/NowPlayingOverlay.tsx b/src/components/player/NowPlayingOverlay.tsx
index a5a4d2d..207a55e 100644
--- a/src/components/player/NowPlayingOverlay.tsx
+++ b/src/components/player/NowPlayingOverlay.tsx
@@ -68,7 +68,7 @@ import {
ScopedPaletteProvider,
useColors,
} from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { motion } from '@/theme/motion';
import { paletteWithAccent } from '@/theme/scopedAccent';
import { useNowPlayingArtworkAccent } from '@/theme/useNowPlayingArtworkAccent';
@@ -193,7 +193,6 @@ export function NowPlayingOverlay({
}: NowPlayingOverlayProps = {}) {
const dock = presentation === 'dock';
const appColors = useColors();
- const ripple = useRipple();
const router = useRouter();
const returnToTabs = useReturnToTabs();
const rawInsets = useSafeAreaInsets();
@@ -1094,9 +1093,9 @@ export function NowPlayingOverlay({
downward, the pane collapses sideways back to the bar
card. Adding a second control alongside this one just
gave the dock two chevrons that did different things. */}
-
dock
? void useSettingsStore.getState().setPlayerDockOpen(false)
@@ -1111,7 +1110,7 @@ export function NowPlayingOverlay({
size={26}
color={colors.textSecondary}
/>
-
+
@@ -1126,25 +1125,25 @@ export function NowPlayingOverlay({
dock used to draw its own expand button on top of this
one, which is what made it read as tacked on. */}
{dock ? (
- usePlayerUiStore.getState().openPlayer()}
hitSlop={12}
accessibilityRole="button"
accessibilityLabel="Open full screen player"
>
-
+
) : null}
-
-
+
)}
@@ -1234,7 +1233,7 @@ export function NowPlayingOverlay({
void sendDesktopControl('toggle-favorite')}
@@ -1272,7 +1271,7 @@ export function NowPlayingOverlay({
void reconnectDesktop()}
accessibilityLabel="Reconnect to desktop"
>
@@ -1409,7 +1408,7 @@ export function NowPlayingOverlay({
{desktopQueue ? (
- {
if (desktopConnection) {
void reconnectDesktop();
@@ -1447,7 +1446,7 @@ export function NowPlayingOverlay({
{desktopConnection ? 'Reconnect' : 'Pair desktop'}
-
+
)
) : track ? (
@@ -1658,7 +1657,7 @@ export function NowPlayingOverlay({
hitSlop={10}
haptic="selection"
style={[styles.creditChip, { height: deck.artistLineHeight }]}
- android_ripple={ripple.bounded}
+
onPress={() => setArtistPickerOpen(true)}
accessibilityRole="button"
accessibilityLabel={`All ${artistCreditTokens.length} artists`}
@@ -1673,7 +1672,7 @@ export function NowPlayingOverlay({
void toggleFavorite(track)}
@@ -1716,7 +1715,7 @@ export function NowPlayingOverlay({
void toggleShuffle()}
accessibilityLabel="Shuffle"
@@ -1737,7 +1736,7 @@ export function NowPlayingOverlay({
onPress={skipToPrevious}
haptic="action"
hitSlop={12}
- style={styles.transportMainBtn} android_ripple={ripple.icon(26)}
+ style={styles.transportMainBtn}
accessibilityLabel="Previous"
>
void cycleRepeat()}
accessibilityLabel="Repeat"
@@ -1811,7 +1810,7 @@ export function NowPlayingOverlay({
void setScopeStageVisible(!scopeStageVisible)}
accessibilityLabel={scopeStageVisible ? 'Hide visualizer' : 'Show visualizer'}
@@ -1839,7 +1838,7 @@ export function NowPlayingOverlay({
{menuItems.map((item) => (
-
{item.label}
-
+
))}
diff --git a/src/components/player/SleepTimerControls.tsx b/src/components/player/SleepTimerControls.tsx
index f8d6be3..dc12ca5 100644
--- a/src/components/player/SleepTimerControls.tsx
+++ b/src/components/player/SleepTimerControls.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
-import { Pressable, TextInput, View } from 'react-native';
+import { TextInput, View } from 'react-native';
import { Text } from '@/components/Text';
import { SLEEP_TIMER_PRESETS, formatSleepTimerStatus, normalizeSleepTimerMinutes } from '@/audio/sleepTimerState';
import { supportsNativePauseAtEndOfItem } from '@/audio/trackPlayerExtensions';
@@ -8,12 +8,11 @@ import { usePlaybackTargetStore } from '@/stores/playbackTargetStore';
import { useSleepTimerStore } from '@/stores/sleepTimerStore';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
export function SleepTimerControls() {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const timer = useSleepTimerStore((s) => s.timer);
const remainingMs = useSleepTimerStore((s) => s.remainingMs);
const hydrate = useSleepTimerStore((s) => s.hydrate);
@@ -76,17 +75,17 @@ export function SleepTimerControls() {
{SLEEP_TIMER_PRESETS.map((minutes) => (
- void run(() => startMinutes(minutes), `Timer set for ${minutes} minutes.`)}
style={({ pressed }) => [styles.preset, !available && styles.disabled, pressed && available && styles.pressed]}
accessibilityRole="button"
>
{minutes} min
-
+
))}
@@ -103,19 +102,19 @@ export function SleepTimerControls() {
style={[styles.input, !available && styles.disabled]}
accessibilityLabel="Custom sleep timer minutes"
/>
- [styles.action, !available && styles.disabled, pressed && available && styles.pressed]}
>
Set custom
-
+
- void run(startEndOfTrack, 'Timer set for the end of the track.')}
style={({ pressed }) => [
styles.fullAction,
@@ -127,12 +126,12 @@ export function SleepTimerControls() {
{supportsNativePauseAtEndOfItem() ? 'Pause exactly before the next track begins.' : 'Requires the Android playback engine.'}
-
+
{timer ? (
- void run(cancel, 'Sleep timer canceled.')} style={styles.cancel}>
+ void run(cancel, 'Sleep timer canceled.')} style={styles.cancel}>
Cancel sleep timer
-
+
) : null}
{feedback ? {feedback} : null}
diff --git a/src/components/queue/RemoteQueueSheet.tsx b/src/components/queue/RemoteQueueSheet.tsx
index 8872963..fac52a2 100644
--- a/src/components/queue/RemoteQueueSheet.tsx
+++ b/src/components/queue/RemoteQueueSheet.tsx
@@ -7,7 +7,7 @@
// queue-tray-sheet gotcha).
import { useCallback, useEffect, useMemo } from 'react';
-import { Pressable, View } from 'react-native';
+import { View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import BottomSheet, {
BottomSheetBackdrop,
@@ -19,7 +19,7 @@ import { FlashList, type ListRenderItemInfo } from '@shopify/flash-list';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { formatDuration } from '@/lib/format';
import { useDesktopRemoteStore } from '@/stores/desktopRemoteStore';
import type { DesktopRemoteQueueItem } from '@/types/desktopRemote';
@@ -33,7 +33,6 @@ interface RemoteQueueSheetProps {
export function RemoteQueueSheet({ onClose, embedded = false }: RemoteQueueSheetProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const queue = useDesktopRemoteStore((s) => s.queue);
const snapPoints = useMemo(() => ['58%', '100%'], []);
const renderFlashListScrollComponent = useBottomSheetScrollableCreator();
@@ -71,8 +70,8 @@ export function RemoteQueueSheet({ onClose, embedded = false }: RemoteQueueSheet
const renderItem = useCallback(
({ item }: ListRenderItemInfo) => (
- playItem(item)}
disabled={item.isCurrent}
@@ -100,9 +99,9 @@ export function RemoteQueueSheet({ onClose, embedded = false }: RemoteQueueSheet
{formatDuration(item.durationSeconds)}
) : null}
-
+
),
- [playItem, colors, styles, ripple]
+ [playItem, colors, styles]
);
const topSection = useMemo(
diff --git a/src/components/search/QuickSearchOverlay.tsx b/src/components/search/QuickSearchOverlay.tsx
index aae9705..c1da68b 100644
--- a/src/components/search/QuickSearchOverlay.tsx
+++ b/src/components/search/QuickSearchOverlay.tsx
@@ -7,7 +7,6 @@ import {
import {
Keyboard,
Modal,
- Pressable,
StyleSheet,
TextInput,
View,
@@ -28,7 +27,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { rgbaFromHex } from '@/theme/colorUtils';
import { enqueueTop, playLibraryQuery } from '@/audio/playbackController';
import { dbTrackToTrack } from '@/library/trackAdapter';
@@ -514,7 +513,6 @@ function ResultRow({
onQueueTrack: (track: DbTrack) => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const isTrack = result.kind === 'track';
const isShowMode = result.kind === 'show-all' || result.kind === 'show-top';
@@ -526,7 +524,7 @@ function ResultRow({
};
return (
-
{isTrack ? (
-
-
+
) : result.kind === 'playlist' && result.playlist.remote ? (
) : (
)}
-
+
);
}
@@ -567,7 +565,6 @@ function QuickSearchPanel({
onClose: () => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const returnToTabs = useReturnToTabs();
const insets = useSafeAreaInsets();
@@ -1084,18 +1081,18 @@ function QuickSearchPanel({
style={styles.input}
/>
{query.length > 0 ? (
- updateQuery('')}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel="Clear search"
>
-
+
) : null}
-
+
-
+
{showAllLibrary ? (
@@ -1145,7 +1142,6 @@ function QuickSearchPanel({
export function QuickSearchOverlay() {
const styles = useStyles();
- const ripple = useRipple();
const isOpen = useSearchStore((s) => s.isQuickSearchOpen);
const initialQuery = useSearchStore((s) => s.initialQuery);
const openVersion = useSearchStore((s) => s.openVersion);
@@ -1165,7 +1161,7 @@ export function QuickSearchOverlay() {
onRequestClose={close}
>
-
+
{isOpen ? (
) : null}
diff --git a/src/components/settings/AccentColorSheet.tsx b/src/components/settings/AccentColorSheet.tsx
index dbad7c2..67156f2 100644
--- a/src/components/settings/AccentColorSheet.tsx
+++ b/src/components/settings/AccentColorSheet.tsx
@@ -2,7 +2,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Modal,
- Pressable,
StyleSheet,
View,
type LayoutChangeEvent,
@@ -23,7 +22,7 @@ import { AppSheet, AppSheetTitle } from '@/components/sheets/AppSheet';
import { Text } from '@/components/Text';
import { fonts, radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import {
hexToHsv,
hsvToHex,
@@ -59,7 +58,6 @@ export function AccentColorSheet({
}: AccentColorSheetProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const normalizedInitial = normalizeHexColor(initialHex) ?? '#5b8aff';
const [hsv, setHsv] = useState(() => hexToHsv(normalizedInitial));
const hsvRef = useRef(hsv);
@@ -245,21 +243,21 @@ export function AccentColorSheet({
-
Cancel
-
-
+
Apply
-
+
diff --git a/src/components/settings/AccentSwatchRow.tsx b/src/components/settings/AccentSwatchRow.tsx
index 97ef18d..11165ff 100644
--- a/src/components/settings/AccentSwatchRow.tsx
+++ b/src/components/settings/AccentSwatchRow.tsx
@@ -1,12 +1,12 @@
import { useState } from 'react';
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Canvas, LinearGradient, Rect, vec } from '@shopify/react-native-skia';
import { Text } from '@/components/Text';
import { spacing } from '@/theme';
import { ACCENTS, ACCENT_IDS, accentPreferenceBase } from '@/theme/accents';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
import { useThemeStore } from '@/stores/themeStore';
import { AccentColorSheet } from '@/components/settings/AccentColorSheet';
@@ -17,7 +17,6 @@ const RAINBOW = ['#ff5c5c', '#ffb454', '#2dd4a0', '#00b3ff', '#9d7bff', '#ff6b9d
/** Circular accent swatches; the selected one gets a ring + checkmark. */
export function AccentSwatchRow() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [pickerOpen, setPickerOpen] = useState(false);
const preference = useThemeStore((s) => s.accentPreference);
@@ -31,7 +30,7 @@ export function AccentSwatchRow() {
{ACCENT_IDS.map((id) => {
const selected = preference.kind === 'preset' && id === preference.id;
return (
- {
if (selected) return;
@@ -51,11 +50,11 @@ export function AccentSwatchRow() {
{selected ? (
) : null}
-
+
);
})}
- {
playHaptic('selection');
setPickerOpen(true);
@@ -86,7 +85,7 @@ export function AccentSwatchRow() {
) : (
)}
-
+
Accent · {preference.kind === 'preset'
diff --git a/src/components/settings/ScopeStyleCards.tsx b/src/components/settings/ScopeStyleCards.tsx
index b75be40..726ec70 100644
--- a/src/components/settings/ScopeStyleCards.tsx
+++ b/src/components/settings/ScopeStyleCards.tsx
@@ -1,8 +1,8 @@
-import { Pressable, StyleSheet, View } from 'react-native';
+import { StyleSheet, View } from 'react-native';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import type { NowPlayingScopeStyle } from '@/stores/settingsStore';
import { playHaptic } from '@/lib/haptics';
@@ -58,7 +58,6 @@ function StyleCard({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const handlePress = () => {
if (selected) return;
@@ -67,8 +66,8 @@ function StyleCard({
};
return (
-
{description}
-
+
);
}
diff --git a/src/components/settings/SettingsPanels.tsx b/src/components/settings/SettingsPanels.tsx
index a56fae4..5243ffb 100644
--- a/src/components/settings/SettingsPanels.tsx
+++ b/src/components/settings/SettingsPanels.tsx
@@ -1,5 +1,4 @@
import {
- Pressable,
StyleSheet,
View,
} from 'react-native';
@@ -18,7 +17,7 @@ import {
} from '@/components/settings/SettingsSectionScaffold';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import type { ReplayGainMode } from '@/audio/normalization';
import type { ArtistGroupingMode } from '@/library/artistGrouping';
import type { BaseThemeId, PreferredDark } from '@/theme/resolve';
@@ -108,7 +107,6 @@ export function themeOptionTitle(id: BaseThemeId): string {
export function AppearanceSettingsPanel() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const baseTheme = useThemeStore((s) => s.baseTheme);
const preferredDark = useThemeStore((s) => s.preferredDark);
@@ -136,7 +134,7 @@ export function AppearanceSettingsPanel() {
{options.map((option) => {
const selected = option.id === baseTheme;
return (
- {
@@ -160,7 +158,7 @@ export function AppearanceSettingsPanel() {
) : (
)}
-
+
);
})}
@@ -249,7 +247,6 @@ function LibraryFolderSettingsRow({
onRemove: (folder: FolderWithCount) => void;
}) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
return (
@@ -272,7 +269,7 @@ function LibraryFolderSettingsRow({
: 'Access lost. Remove and add again.'}
- onRemove(folder)}
@@ -281,14 +278,13 @@ function LibraryFolderSettingsRow({
style={disabled && styles.actionDisabled}
>
-
+
);
}
function LibraryFoldersSettings() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const folders = useLibraryStore((s) => s.folders);
const isScanning = useLibraryStore((s) => s.isScanning);
@@ -326,7 +322,7 @@ function LibraryFoldersSettings() {
: `${formatFolderCount(folders.length)} / ${formatTrackCount(totalTracks)}`}
- void addFolder()}
@@ -336,12 +332,12 @@ function LibraryFoldersSettings() {
Add
-
+
{folders.length > 0 ? (
- void rescan()}
@@ -351,7 +347,7 @@ function LibraryFoldersSettings() {
Rescan all
-
+
) : null}
@@ -387,7 +383,6 @@ function LibraryFoldersSettings() {
export function LibrarySettingsPanel() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const groupingMode = useSettingsStore((s) => s.artistGroupingMode);
const setArtistGroupingMode = useSettingsStore((s) => s.setArtistGroupingMode);
@@ -453,7 +448,7 @@ export function LibrarySettingsPanel() {
{ARTIST_GROUPING_OPTIONS.map((option) => {
const selected = option.mode === groupingMode;
return (
- {
@@ -477,7 +472,7 @@ export function LibrarySettingsPanel() {
) : (
)}
-
+
);
})}
@@ -506,7 +501,6 @@ export function LibrarySettingsPanel() {
export function AudioSettingsPanel() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const normalizationEnabled = useAudioSettingsStore((s) => s.normalizationEnabled);
const normalizationTargetLufs = useAudioSettingsStore((s) => s.normalizationTargetLufs);
@@ -554,7 +548,7 @@ export function AudioSettingsPanel() {
{REPLAYGAIN_MODES.map((m) => {
const selected = m.mode === replayGainMode;
return (
- {
@@ -566,7 +560,7 @@ export function AudioSettingsPanel() {
{m.label}
-
+
);
})}
diff --git a/src/components/settings/SettingsSectionScaffold.tsx b/src/components/settings/SettingsSectionScaffold.tsx
index aada7b7..dd4411a 100644
--- a/src/components/settings/SettingsSectionScaffold.tsx
+++ b/src/components/settings/SettingsSectionScaffold.tsx
@@ -1,6 +1,5 @@
import type { ReactNode } from 'react';
import {
- Pressable,
StyleSheet,
View,
type StyleProp,
@@ -14,7 +13,7 @@ import { ScreenHeader, useScreenHeader } from '@/components/ScreenHeader';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { HapticSwitch } from '@/components/HapticSwitch';
export type SettingsIconName = keyof typeof Ionicons.glyphMap;
@@ -101,9 +100,8 @@ export function SettingsNavRow({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
return (
-
+
@@ -119,7 +117,7 @@ export function SettingsNavRow({
-
+
);
}
diff --git a/src/components/sheets/ActionSheet.tsx b/src/components/sheets/ActionSheet.tsx
index ee88d5b..7174904 100644
--- a/src/components/sheets/ActionSheet.tsx
+++ b/src/components/sheets/ActionSheet.tsx
@@ -12,7 +12,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { AppPressable, SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { useSceneBottomInset } from '@/navigation/useShellLayout';
export interface ActionSheetItem {
@@ -41,7 +41,6 @@ export function ActionSheet({
}) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const insets = useSafeAreaInsets();
const sceneBottomInset = useSceneBottomInset();
@@ -62,9 +61,9 @@ export function ActionSheet({
) : null}
{items.map((item) => (
-
) : null}
-
+
))}
diff --git a/src/components/sheets/AppSheet.tsx b/src/components/sheets/AppSheet.tsx
index 5ef8c84..12d933c 100644
--- a/src/components/sheets/AppSheet.tsx
+++ b/src/components/sheets/AppSheet.tsx
@@ -17,7 +17,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { SCROLL_PRESS_DELAY, useRipple } from '@/theme/ripple';
+import { SCROLL_PRESS_DELAY } from '@/components/AppPressable';
import { playHaptic } from '@/lib/haptics';
import { useSceneBottomInset } from '@/navigation/useShellLayout';
@@ -121,7 +121,6 @@ export function AppSheetItem({
}: AppSheetItemProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const tint = destructive ? colors.warning : selected ? colors.accentTextStrong : colors.textPrimary;
const selectable = selected !== undefined;
@@ -139,9 +138,8 @@ export function AppSheetItem({
later contact, leaving the row visibly tapped but never pressed.
*/}
[styles.item, pressed && styles.itemPressed]}
onPress={handlePress}
accessibilityRole={selectable ? 'radio' : 'button'}
accessibilityState={selectable ? { selected } : undefined}
@@ -205,6 +203,9 @@ const useStyles = createThemedStyles((colors) => ({
gap: spacing.md,
paddingVertical: spacing.md,
},
+ itemPressed: {
+ backgroundColor: colors.glassHighlight,
+ },
itemLabel: {
flexShrink: 1,
},
diff --git a/src/components/sheets/PlaylistPickerSheet.tsx b/src/components/sheets/PlaylistPickerSheet.tsx
index c615acf..8f43d93 100644
--- a/src/components/sheets/PlaylistPickerSheet.tsx
+++ b/src/components/sheets/PlaylistPickerSheet.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react';
import {
- Pressable,
StyleSheet,
View
} from 'react-native';
@@ -17,7 +16,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { usePlaylistStore } from '@/stores/playlistStore';
import type { DbTrack } from '@/types/library';
@@ -42,7 +41,6 @@ export function PlaylistPickerSheet({
onAdded,
}: PlaylistPickerSheetProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [step, setStep] = useState<'pick' | 'create'>('pick');
const [playlistName, setPlaylistName] = useState('');
@@ -84,12 +82,12 @@ export function PlaylistPickerSheet({
selectionColor={colors.accent}
/>
- setStep('pick')}>
+ setStep('pick')}>
Back
-
-
+
Create
-
+
);
diff --git a/src/components/sheets/TextPromptModal.tsx b/src/components/sheets/TextPromptModal.tsx
index fb2f0c6..02e4ae5 100644
--- a/src/components/sheets/TextPromptModal.tsx
+++ b/src/components/sheets/TextPromptModal.tsx
@@ -14,7 +14,7 @@ import {
spacing,
} from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
interface TextPromptModalProps {
visible: boolean;
@@ -42,7 +42,6 @@ function TextPromptModalInner({
onClose,
}: TextPromptModalProps) {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const [value, setValue] = useState(initialValue);
const trimmed = value.trim();
@@ -71,12 +70,12 @@ function TextPromptModalInner({
selectionColor={colors.accent}
/>
-
+
Cancel
-
-
+
{submitLabel}
-
+
diff --git a/src/components/signal/SignalResolutionPanel.tsx b/src/components/signal/SignalResolutionPanel.tsx
index 295eab4..3bfac50 100644
--- a/src/components/signal/SignalResolutionPanel.tsx
+++ b/src/components/signal/SignalResolutionPanel.tsx
@@ -1,4 +1,4 @@
-import { Pressable, ScrollView, StyleSheet, View } from 'react-native';
+import { ScrollView, StyleSheet, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Text } from '@/components/Text';
import { TrackRow } from '@/components/library/TrackRow';
@@ -8,7 +8,7 @@ import type { DbTrack } from '@/types/library';
import type { SignalPayload } from '@boof2015/astra-signal';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
export type SignalResultActionState = 'idle' | 'playing' | 'queueing' | 'queued';
@@ -37,12 +37,11 @@ export function SignalResolutionPanel({
}: SignalResolutionPanelProps) {
const styles = useStyles();
const colors = useColors();
- const ripple = useRipple();
const actionBusy = actionState === 'playing' || actionState === 'queueing';
const onlineAction = (primary = false) => (
-
Find online
-
+
);
const footer = (
-
Scan another
-
-
+
Done
-
+
);
@@ -192,8 +191,8 @@ export function SignalResolutionPanel({
/>
- onPlay(track)}
disabled={actionBusy}
@@ -203,9 +202,9 @@ export function SignalResolutionPanel({
{actionState === 'playing' ? 'Starting…' : 'Play now'}
-
-
+ onQueue(track)}
disabled={actionBusy || actionState === 'queued'}
@@ -223,7 +222,7 @@ export function SignalResolutionPanel({
? 'Added'
: 'Add to queue'}
-
+
{onlineAction()}
diff --git a/src/components/sync/SyncConflictPrompt.tsx b/src/components/sync/SyncConflictPrompt.tsx
index eeef211..d674ee9 100644
--- a/src/components/sync/SyncConflictPrompt.tsx
+++ b/src/components/sync/SyncConflictPrompt.tsx
@@ -13,7 +13,7 @@ import { router, usePathname } from 'expo-router';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles, useColors } from '@/theme/themed';
-import { useRipple } from '@/theme/ripple';
+import { AppPressable } from '@/components/AppPressable';
import { formatRelativeTime } from '@/lib/format';
import {
buildSyncConflictResolutionPreview,
@@ -65,7 +65,6 @@ function diffLine(desktop: SyncPlaylistSnapshot, phone: SyncPlaylistSnapshot): s
export function SyncConflictPrompt() {
const styles = useStyles();
- const ripple = useRipple();
const colors = useColors();
const conflicts = useDesktopSyncStore((s) => s.conflicts);
const status = useDesktopSyncStore((s) => s.status);
@@ -160,7 +159,7 @@ export function SyncConflictPrompt() {
{options.map((resolution) => (
- setChoice({ syncUid: firstConflict.syncUid, resolution })}
>
{RESOLUTION_LABELS[resolution]}
-
+
))}
@@ -185,20 +184,20 @@ export function SyncConflictPrompt() {
{count > 1 ? (
-
+
Review all {count} conflicts
-
+
) : null}
-
+
Not now
-
-
+
Confirm
-
+
diff --git a/src/theme/palettes.ts b/src/theme/palettes.ts
index 6db594c..c5fe261 100644
--- a/src/theme/palettes.ts
+++ b/src/theme/palettes.ts
@@ -5,7 +5,7 @@
* INVARIANT: solid-color tokens must stay 6-digit `#rrggbb` hex — the Skia
* visualizers (SpectrumCurve, OscilloscopeWave, EQGraph, GraphicResponseCurve)
* slice hex chars to build alpha variants. Only `glass*`, `*Glow`,
- * `overlayFaint`, `ripple`, and `backdrop` may be `rgba()` strings (they are
+ * `overlayFaint` and `backdrop` may be `rgba()` strings (they are
* never fed through those helpers).
*/
@@ -26,8 +26,6 @@ export interface Palette {
*/
glassBorderStrong: string;
glassHighlight: string;
- /** Native android_ripple tint (glassHighlight family, transient so slightly stronger). */
- ripple: string;
// Text ramp
textPrimary: string;
@@ -75,7 +73,6 @@ export const midnightBase: BasePalette = {
glassBorder: 'rgba(124, 146, 196, 0.16)',
glassBorderStrong: 'rgba(124, 146, 196, 0.34)',
glassHighlight: 'rgba(140, 162, 208, 0.08)',
- ripple: 'rgba(140, 162, 208, 0.13)',
textPrimary: '#e2e8f4',
textSecondary: '#8a98b8',
@@ -104,7 +101,6 @@ export const darkBase: BasePalette = {
glassBorder: 'rgba(165, 175, 195, 0.16)',
glassBorderStrong: 'rgba(165, 175, 195, 0.34)',
glassHighlight: 'rgba(175, 185, 205, 0.08)',
- ripple: 'rgba(175, 185, 205, 0.13)',
textPrimary: '#e6e8ec',
textSecondary: '#979da8',
@@ -134,7 +130,6 @@ export const amoledBase: BasePalette = {
glassBorder: 'rgba(124, 146, 196, 0.20)',
glassBorderStrong: 'rgba(124, 146, 196, 0.38)',
glassHighlight: 'rgba(140, 162, 208, 0.10)',
- ripple: 'rgba(140, 162, 208, 0.14)',
textPrimary: '#e2e8f4',
textSecondary: '#8a98b8',
@@ -164,7 +159,6 @@ export const lightBase: BasePalette = {
glassBorder: 'rgba(52, 74, 130, 0.18)',
glassBorderStrong: 'rgba(52, 74, 130, 0.32)',
glassHighlight: 'rgba(52, 74, 130, 0.10)',
- ripple: 'rgba(52, 74, 130, 0.12)',
textPrimary: '#171d2e',
textSecondary: '#4d5a78',
diff --git a/src/theme/resolve.ts b/src/theme/resolve.ts
index e2f4d2c..37e93c7 100644
--- a/src/theme/resolve.ts
+++ b/src/theme/resolve.ts
@@ -84,7 +84,6 @@ export function buildMaterialYouPalette(ramps: SystemPalette, isDark: boolean):
glassBorder: rgbaFromHex(tone(ramps.accent2, 200), 0.18),
glassBorderStrong: rgbaFromHex(tone(ramps.accent2, 200), 0.36),
glassHighlight: rgbaFromHex(tone(ramps.accent2, 200), 0.09),
- ripple: rgbaFromHex(tone(ramps.accent2, 200), 0.13),
textPrimary: tone(ramps.neutral1, 50),
textSecondary: tone(ramps.neutral2, 300),
textTertiary: tone(ramps.neutral2, 500),
@@ -110,7 +109,6 @@ export function buildMaterialYouPalette(ramps: SystemPalette, isDark: boolean):
glassBorder: rgbaFromHex(tone(ramps.neutral2, 700), 0.16),
glassBorderStrong: rgbaFromHex(tone(ramps.neutral2, 700), 0.32),
glassHighlight: rgbaFromHex(tone(ramps.neutral2, 700), 0.09),
- ripple: rgbaFromHex(tone(ramps.neutral2, 700), 0.12),
textPrimary: tone(ramps.neutral1, 900),
textSecondary: tone(ramps.neutral2, 700),
textTertiary: tone(ramps.neutral2, 500),
diff --git a/src/theme/ripple.ts b/src/theme/ripple.ts
deleted file mode 100644
index 1c8f034..0000000
--- a/src/theme/ripple.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-// Native android_ripple configs — touch feedback drawn by the Android render
-// thread the frame the finger lands, independent of JS-thread load. This is
-// the app's ONE press-feedback system: JS `({ pressed })` dim styles were
-// removed in its favor (persistent selected/active styles are state, not
-// feedback, and stay).
-
-import { useMemo } from 'react';
-import type { PressableAndroidRippleConfig } from 'react-native';
-import { useColors } from '@/theme/themed';
-
-/**
- * Fixed light overlay for accent-filled buttons — the theme `ripple` token is
- * invisible on a saturated accent fill. Theme-invariant on purpose (Material's
- * on-color ripple), so it is not a palette token.
- */
-const ON_ACCENT_COLOR = 'rgba(255, 255, 255, 0.24)';
-
-/**
- * `unstable_pressDelay` for Pressables inside SCROLLABLE content (rows, tiles,
- * sheet items): a scroll flick claims the touch before the delay elapses, so
- * scrolling never flashes ripples on everything it grazes — mirroring Android's
- * ViewConfiguration tap timeout in native lists. Quick real taps still ripple
- * (RN fires press-in/out on release even under the delay). Fixed chrome
- * (tab bar, transport, action bars) stays instant — no delay there.
- */
-export const SCROLL_PRESS_DELAY = 80;
-
-export interface RippleSet {
- /** Clips to the Pressable rect/borderRadius — rows, cards, pills, sheet items. */
- bounded: PressableAndroidRippleConfig;
- /** Bounded but drawn ABOVE children — tiles/cards whose artwork covers the Pressable. */
- tile: PressableAndroidRippleConfig;
- /** Circular unbounded ripple for transparent icon buttons; radius ≈ half the touch target. */
- icon: (radius: number) => PressableAndroidRippleConfig;
- /** Light overlay for accent-filled buttons (bounded unless a radius is given). */
- onAccent: (radius?: number) => PressableAndroidRippleConfig;
-}
-
-export function useRipple(): RippleSet {
- const colors = useColors();
- return useMemo(
- () => ({
- bounded: { color: colors.ripple },
- tile: { color: colors.ripple, foreground: true },
- icon: (radius: number) => ({ color: colors.ripple, borderless: true, radius }),
- onAccent: (radius?: number) =>
- radius == null
- ? { color: ON_ACCENT_COLOR, foreground: true }
- : { color: ON_ACCENT_COLOR, borderless: true, radius },
- }),
- [colors.ripple]
- );
-}