mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-19 12:14:47 +02:00
theming + material you
This commit is contained in:
@@ -7,14 +7,15 @@ import { Image } from 'expo-image';
|
||||
import { Text } from '@/components/Text';
|
||||
import { AstraLogo } from '@/components/AstraLogo';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles } from '@/theme/themed';
|
||||
import { albumArtworkSource } from '@/library/artwork';
|
||||
import type { Album } from '@/types/library';
|
||||
|
||||
export function AlbumGridItem({ album, onPress }: { album: Album; onPress: () => void }) {
|
||||
const styles = useStyles();
|
||||
const artUri = albumArtworkSource(album);
|
||||
return (
|
||||
<Pressable style={styles.item} onPress={onPress} accessibilityRole="button">
|
||||
@@ -41,7 +42,7 @@ export function AlbumGridItem({ album, onPress }: { album: Album; onPress: () =>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
item: {
|
||||
flex: 1,
|
||||
marginBottom: spacing.lg,
|
||||
@@ -64,4 +65,4 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -7,15 +7,17 @@ import { Image } from 'expo-image';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { albumArtworkSource } from '@/library/artwork';
|
||||
import type { Album } from '@/types/library';
|
||||
|
||||
/** Compact album list row (search results) — the grid uses AlbumGridItem. */
|
||||
export function AlbumRow({ album, onPress }: { album: Album; onPress: () => void }) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const artUri = albumArtworkSource(album);
|
||||
return (
|
||||
<Pressable style={styles.row} onPress={onPress} accessibilityRole="button">
|
||||
@@ -43,7 +45,7 @@ export function AlbumRow({ album, onPress }: { album: Album; onPress: () => void
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -71,4 +73,4 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -4,7 +4,9 @@ import { StyleSheet, View, type LayoutChangeEvent } from 'react-native';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
|
||||
import { Text } from '@/components/Text';
|
||||
import { colors, radius, spacing } from '@/theme';
|
||||
import { radius, spacing } from '@/theme';
|
||||
import { createThemedStyles } from '@/theme/themed';
|
||||
import { rgbaFromHex } from '@/theme/colorUtils';
|
||||
import { tickHaptic } from '@/lib/haptics';
|
||||
import { usePullSearchGestureRef } from '@/components/search/PullSearchGesture';
|
||||
import { RAIL_LETTERS } from '@/lib/letterIndex';
|
||||
@@ -29,6 +31,7 @@ interface AlphabetRailProps {
|
||||
* scroll-top never arms the search indicator.
|
||||
*/
|
||||
export function AlphabetRail({ activeLetters, onJumpToLetter }: AlphabetRailProps) {
|
||||
const styles = useStyles();
|
||||
const pullSearchRef = usePullSearchGestureRef();
|
||||
const [scrubLetter, setScrubLetter] = useState<string | null>(null);
|
||||
const lastLetter = useSharedValue('');
|
||||
@@ -128,7 +131,7 @@ export function AlphabetRail({ activeLetters, onJumpToLetter }: AlphabetRailProp
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
wrap: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@@ -144,7 +147,7 @@ const styles = StyleSheet.create({
|
||||
width: 16,
|
||||
paddingVertical: RAIL_PAD,
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(8, 10, 15, 0.35)',
|
||||
backgroundColor: rgbaFromHex(colors.bgPrimary, 0.35),
|
||||
borderRadius: radius.pill,
|
||||
},
|
||||
cell: {
|
||||
@@ -189,4 +192,4 @@ const styles = StyleSheet.create({
|
||||
lineHeight: 30,
|
||||
color: colors.accentTextStrong,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -7,15 +7,17 @@ import { Image } from 'expo-image';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
spacing,
|
||||
radius
|
||||
radius,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { artworkUri } from '@/library/artwork';
|
||||
import type { Artist } from '@/types/library';
|
||||
|
||||
/** 2-column grid cell: square art (2x2 album mosaic when available) + counts, matching the album grid. */
|
||||
export function ArtistGridItem({ artist, onPress }: { artist: Artist; onPress: () => void }) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const useMosaic = artist.artwork_hashes.length >= 4;
|
||||
const hashes = useMosaic ? artist.artwork_hashes.slice(0, 4) : artist.artwork_hashes.slice(0, 1);
|
||||
|
||||
@@ -58,7 +60,7 @@ export function ArtistGridItem({ artist, onPress }: { artist: Artist; onPress: (
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
item: {
|
||||
flex: 1,
|
||||
marginBottom: spacing.lg,
|
||||
@@ -87,4 +89,4 @@ const styles = StyleSheet.create({
|
||||
name: {
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -7,14 +7,16 @@ import { Image } from 'expo-image';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
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();
|
||||
return (
|
||||
<Pressable style={styles.row} onPress={onPress} accessibilityRole="button">
|
||||
<View style={styles.art}>
|
||||
@@ -41,7 +43,7 @@ export function ArtistRow({ artist, onPress }: { artist: Artist; onPress: () =>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -66,4 +68,4 @@ const styles = StyleSheet.create({
|
||||
meta: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -30,10 +30,10 @@ import {
|
||||
} from '@shopify/react-native-skia';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
|
||||
// Collapsing detail header. An absolute container whose height shrinks with the
|
||||
// scroll and clips its faded content, so the track list (padded to the expanded
|
||||
@@ -97,6 +97,8 @@ export function useDetailCollapse() {
|
||||
}
|
||||
|
||||
function BottomFade() {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const [width, setWidth] = useState(0);
|
||||
return (
|
||||
<View style={styles.fade} onLayout={(e) => setWidth(e.nativeEvent.layout.width)}>
|
||||
@@ -152,6 +154,8 @@ export function CollapsingHeader({
|
||||
expandedHeight: number;
|
||||
onHeroBlockLayout: (e: LayoutChangeEvent) => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { width: W } = useWindowDimensions();
|
||||
const dist = expandedHeight - BAR_H;
|
||||
@@ -330,7 +334,7 @@ export function CollapsingHeader({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@@ -481,4 +485,4 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Pressable
|
||||
} from 'react-native';
|
||||
@@ -7,12 +6,14 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
|
||||
export function EmptyLibrary() {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
@@ -34,7 +35,7 @@ export function EmptyLibrary() {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
empty: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
@@ -63,4 +64,4 @@ const styles = StyleSheet.create({
|
||||
color: colors.bgPrimary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -32,10 +32,10 @@ import {
|
||||
} from '@/library/folderTree';
|
||||
import { formatDuration } from '@/lib/format';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { useLibraryStore } from '@/stores/libraryStore';
|
||||
import { usePlayerStore } from '@/stores/playerStore';
|
||||
import type { DbTrack } from '@/types/library';
|
||||
@@ -58,6 +58,8 @@ function FolderRow({
|
||||
onShuffle: (node: FolderTreeNode) => void;
|
||||
onOpenActions: (node: FolderTreeNode) => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const { node, depth, isExpanded } = row;
|
||||
|
||||
const play = (event: GestureResponderEvent) => {
|
||||
@@ -132,6 +134,8 @@ function FolderTrackRow({
|
||||
active: boolean;
|
||||
onOpenActions: () => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const index = row.folderTracks.findIndex((track) => track.path === row.track.path);
|
||||
|
||||
const playFolderTrack = () => {
|
||||
@@ -180,6 +184,8 @@ function FolderTrackRow({
|
||||
}
|
||||
|
||||
export function FoldersView({ onScroll, scrollEventThrottle }: FoldersViewProps) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const folders = useLibraryStore((s) => s.folders);
|
||||
const tracks = useLibraryStore((s) => s.tracks);
|
||||
const currentPath = usePlayerStore((s) => s.currentTrack?.path);
|
||||
@@ -306,7 +312,7 @@ export function FoldersView({ onScroll, scrollEventThrottle }: FoldersViewProps)
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
listContent: {
|
||||
paddingBottom: spacing.xxl,
|
||||
},
|
||||
@@ -398,4 +404,4 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
maxWidth: 260,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Image } from 'expo-image';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Text } from '@/components/Text';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { artworkUri } from '@/library/artwork';
|
||||
|
||||
export function PlaylistRow({
|
||||
@@ -37,6 +37,8 @@ export function PlaylistRow({
|
||||
onPress: () => void;
|
||||
onLongPress?: () => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.row}
|
||||
@@ -84,7 +86,7 @@ export function PlaylistRow({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -120,4 +122,4 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
flexShrink: 1,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -20,10 +20,10 @@ import { TextPromptModal } from '@/components/sheets/TextPromptModal';
|
||||
import { PlaylistRow } from '@/components/library/PlaylistRow';
|
||||
import { PullSearchScrollView } from '@/components/search/PullSearchGesture';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { usePlaylistStore } from '@/stores/playlistStore';
|
||||
import type { Playlist } from '@/types/playlist';
|
||||
|
||||
@@ -46,6 +46,8 @@ export function PlaylistsView({
|
||||
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
scrollEventThrottle?: number;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const router = useRouter();
|
||||
const playlists = usePlaylistStore((s) => s.playlists);
|
||||
const favoriteCount = usePlaylistStore((s) => s.favoriteTracks.length);
|
||||
@@ -273,7 +275,7 @@ export function PlaylistsView({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
@@ -308,4 +310,4 @@ const styles = StyleSheet.create({
|
||||
borderRadius: radius.pill,
|
||||
backgroundColor: colors.accentGlow,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import { Text } from '@/components/Text';
|
||||
import { colors, spacing } from '@/theme';
|
||||
import { spacing } from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { useLibraryStore } from '@/stores/libraryStore';
|
||||
|
||||
/** Thin accent bar + caption shown under the library header while scanning. */
|
||||
export function ScanProgress() {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const isScanning = useLibraryStore((s) => s.isScanning);
|
||||
const progress = useLibraryStore((s) => s.scanProgress);
|
||||
|
||||
@@ -42,7 +45,7 @@ export function ScanProgress() {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
container: {
|
||||
gap: spacing.xs,
|
||||
marginBottom: spacing.md,
|
||||
@@ -61,4 +64,4 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
opacity: 0.35,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
} from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Text } from '@/components/Text';
|
||||
import { colors, spacing } from '@/theme';
|
||||
import { spacing } from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
|
||||
interface SelectionActionBarProps {
|
||||
count: number;
|
||||
@@ -21,6 +22,7 @@ export function SelectionActionBar({
|
||||
onAddToQueue,
|
||||
onAddToPlaylist,
|
||||
}: SelectionActionBarProps) {
|
||||
const styles = useStyles();
|
||||
const disabled = count === 0;
|
||||
return (
|
||||
<View style={styles.bar}>
|
||||
@@ -62,6 +64,8 @@ function BarButton({
|
||||
disabled: boolean;
|
||||
onPress: () => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
@@ -82,7 +86,7 @@ function BarButton({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
bar: {
|
||||
flexDirection: 'row',
|
||||
borderTopColor: colors.glassBorder,
|
||||
@@ -106,4 +110,4 @@ const styles = StyleSheet.create({
|
||||
label: {
|
||||
color: colors.accent,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -13,10 +13,10 @@ import { FormatBadges } from '@/components/FormatBadge';
|
||||
import { RemoteSourceBadge } from '@/components/RemoteSourceBadge';
|
||||
import { SwipeableRow } from '@/components/SwipeableRow';
|
||||
import {
|
||||
colors,
|
||||
radius,
|
||||
spacing
|
||||
spacing,
|
||||
} from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import { formatDuration } from '@/lib/format';
|
||||
import { trackArtworkThumbSource } from '@/library/artwork';
|
||||
import { dbTrackToTrack } from '@/library/trackAdapter';
|
||||
@@ -58,6 +58,8 @@ export function TrackRow({
|
||||
selected?: boolean;
|
||||
onToggleSelect?: () => void;
|
||||
}) {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
// 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;
|
||||
@@ -174,7 +176,7 @@ export function TrackRow({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -258,4 +260,4 @@ const styles = StyleSheet.create({
|
||||
actionsButtonPressed: {
|
||||
backgroundColor: colors.glassBg,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user