mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-03-15 01:37:33 +08:00
Refactor components for consistent styling and improve button animations
This commit is contained in:
@@ -1,21 +1,21 @@
|
|||||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
|
||||||
import { useFonts } from 'expo-font';
|
import { useFonts } from "expo-font";
|
||||||
import { Stack } from 'expo-router';
|
import { Stack } from "expo-router";
|
||||||
import * as SplashScreen from 'expo-splash-screen';
|
import * as SplashScreen from "expo-splash-screen";
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { Platform, useColorScheme } from 'react-native';
|
import { Platform, useColorScheme } from "react-native";
|
||||||
|
|
||||||
import { useSettingsStore } from '@/stores/settingsStore';
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
|
||||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme() ?? "dark";
|
||||||
const [loaded, error] = useFonts({
|
const [loaded, error] = useFonts({
|
||||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||||
});
|
});
|
||||||
const initializeSettings = useSettingsStore(state => state.loadSettings);
|
const initializeSettings = useSettingsStore((state) => state.loadSettings);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeSettings();
|
initializeSettings();
|
||||||
@@ -35,11 +35,11 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="detail" options={{ headerShown: false }} />
|
<Stack.Screen name="detail" options={{ headerShown: false }} />
|
||||||
{Platform.OS !== 'web' && <Stack.Screen name="play" options={{ headerShown: false }} />}
|
{Platform.OS !== "web" && <Stack.Screen name="play" options={{ headerShown: false }} />}
|
||||||
<Stack.Screen name="search" options={{ headerShown: false }} />
|
<Stack.Screen name="search" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export default function DetailScreen() {
|
|||||||
{item.episodes.length > 1 && (
|
{item.episodes.length > 1 && (
|
||||||
<View style={styles.badge}>
|
<View style={styles.badge}>
|
||||||
<Text style={styles.badgeText}>
|
<Text style={styles.badgeText}>
|
||||||
{item.episodes.length > 99 ? "99+" : `${item.episodes.length}`}
|
{item.episodes.length > 99 ? "99+" : `${item.episodes.length}`} 集
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -275,7 +275,7 @@ const styles = StyleSheet.create({
|
|||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
},
|
},
|
||||||
sourceButton: {
|
sourceButton: {
|
||||||
margin: 5,
|
margin: 8,
|
||||||
},
|
},
|
||||||
sourceButtonText: {
|
sourceButtonText: {
|
||||||
color: "white",
|
color: "white",
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export default function HomeScreen() {
|
|||||||
text={item.title}
|
text={item.title}
|
||||||
onPress={() => handleCategorySelect(item)}
|
onPress={() => handleCategorySelect(item)}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
variant="primary"
|
|
||||||
style={styles.categoryButton}
|
style={styles.categoryButton}
|
||||||
textStyle={styles.categoryText}
|
textStyle={styles.categoryText}
|
||||||
/>
|
/>
|
||||||
@@ -190,16 +189,16 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
// Category Selector
|
// Category Selector
|
||||||
categoryContainer: {
|
categoryContainer: {
|
||||||
paddingBottom: 10,
|
paddingBottom: 6,
|
||||||
},
|
},
|
||||||
categoryListContent: {
|
categoryListContent: {
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
categoryButton: {
|
categoryButton: {
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 2,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginHorizontal: 5,
|
marginHorizontal: 6,
|
||||||
},
|
},
|
||||||
categoryText: {
|
categoryText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export const EpisodeSelectionModal: React.FC<EpisodeSelectionModalProps> = () =>
|
|||||||
isSelected={selectedEpisodeGroup === groupIndex}
|
isSelected={selectedEpisodeGroup === groupIndex}
|
||||||
style={styles.episodeGroupButton}
|
style={styles.episodeGroupButton}
|
||||||
textStyle={styles.episodeGroupButtonText}
|
textStyle={styles.episodeGroupButtonText}
|
||||||
variant="primary"
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
@@ -56,6 +55,7 @@ export const EpisodeSelectionModal: React.FC<EpisodeSelectionModalProps> = () =>
|
|||||||
(selectedEpisodeGroup + 1) * episodeGroupSize
|
(selectedEpisodeGroup + 1) * episodeGroupSize
|
||||||
)}
|
)}
|
||||||
numColumns={5}
|
numColumns={5}
|
||||||
|
contentContainerStyle={styles.episodeList}
|
||||||
keyExtractor={(_, index) => `episode-${selectedEpisodeGroup * episodeGroupSize + index}`}
|
keyExtractor={(_, index) => `episode-${selectedEpisodeGroup * episodeGroupSize + index}`}
|
||||||
renderItem={({ item, index }) => {
|
renderItem={({ item, index }) => {
|
||||||
const absoluteIndex = selectedEpisodeGroup * episodeGroupSize + index;
|
const absoluteIndex = selectedEpisodeGroup * episodeGroupSize + index;
|
||||||
@@ -71,8 +71,6 @@ export const EpisodeSelectionModal: React.FC<EpisodeSelectionModalProps> = () =>
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledButton text="关闭" onPress={onClose} style={styles.closeButton} />
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -87,22 +85,25 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
modalContent: {
|
modalContent: {
|
||||||
width: 400,
|
width: 600,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
modalTitle: {
|
modalTitle: {
|
||||||
color: "white",
|
color: "white",
|
||||||
marginBottom: 20,
|
marginBottom: 12,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
|
episodeList: {
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
},
|
||||||
episodeItem: {
|
episodeItem: {
|
||||||
paddingVertical: 12,
|
paddingVertical: 2,
|
||||||
margin: 4,
|
margin: 4,
|
||||||
flex: 1,
|
width: "18%",
|
||||||
},
|
},
|
||||||
episodeItemText: {
|
episodeItemText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -111,20 +112,13 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
marginBottom: 15,
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
},
|
},
|
||||||
episodeGroupButton: {
|
episodeGroupButton: {
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 6,
|
||||||
paddingVertical: 6,
|
margin: 8,
|
||||||
borderRadius: 15,
|
|
||||||
margin: 5,
|
|
||||||
},
|
},
|
||||||
episodeGroupButtonText: {
|
episodeGroupButtonText: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
closeButton: {
|
|
||||||
padding: 15,
|
|
||||||
marginTop: 20,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Pressable, StyleSheet, StyleProp, ViewStyle, PressableProps, TextStyle, useColorScheme } from "react-native";
|
import {
|
||||||
|
Animated,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
StyleProp,
|
||||||
|
ViewStyle,
|
||||||
|
PressableProps,
|
||||||
|
TextStyle,
|
||||||
|
useColorScheme,
|
||||||
|
} from "react-native";
|
||||||
import { ThemedText } from "./ThemedText";
|
import { ThemedText } from "./ThemedText";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
|
import { useButtonAnimation } from "@/hooks/useButtonAnimation";
|
||||||
|
|
||||||
interface StyledButtonProps extends PressableProps {
|
interface StyledButtonProps extends PressableProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -21,8 +31,10 @@ export const StyledButton: React.FC<StyledButtonProps> = ({
|
|||||||
textStyle,
|
textStyle,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const colorScheme = useColorScheme() ?? "light";
|
const colorScheme = useColorScheme() ?? "dark";
|
||||||
const colors = Colors[colorScheme];
|
const colors = Colors[colorScheme];
|
||||||
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
|
const animationStyle = useButtonAnimation(isSelected, isFocused);
|
||||||
|
|
||||||
const variantStyles = {
|
const variantStyles = {
|
||||||
default: StyleSheet.create({
|
default: StyleSheet.create({
|
||||||
@@ -56,7 +68,6 @@ export const StyledButton: React.FC<StyledButtonProps> = ({
|
|||||||
},
|
},
|
||||||
selectedButton: {
|
selectedButton: {
|
||||||
backgroundColor: "rgba(0, 122, 255, 0.3)",
|
backgroundColor: "rgba(0, 122, 255, 0.3)",
|
||||||
transform: [{ scale: 1.1 }],
|
|
||||||
},
|
},
|
||||||
selectedText: {
|
selectedText: {
|
||||||
color: colors.link,
|
color: colors.link,
|
||||||
@@ -79,10 +90,9 @@ export const StyledButton: React.FC<StyledButtonProps> = ({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
margin: 5,
|
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: "transparent",
|
borderColor: "transparent",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -92,7 +102,6 @@ export const StyledButton: React.FC<StyledButtonProps> = ({
|
|||||||
focusedButton: {
|
focusedButton: {
|
||||||
backgroundColor: colors.link,
|
backgroundColor: colors.link,
|
||||||
borderColor: colors.background,
|
borderColor: colors.background,
|
||||||
transform: [{ scale: 1.1 }],
|
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
shadowColor: colors.link,
|
shadowColor: colors.link,
|
||||||
shadowOffset: { width: 0, height: 0 },
|
shadowOffset: { width: 0, height: 0 },
|
||||||
@@ -113,30 +122,33 @@ export const StyledButton: React.FC<StyledButtonProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Animated.View style={[animationStyle, style]}>
|
||||||
style={({ focused }) => [
|
<Pressable
|
||||||
styles.button,
|
onFocus={() => setIsFocused(true)}
|
||||||
variantStyles[variant].button,
|
onBlur={() => setIsFocused(false)}
|
||||||
isSelected && (variantStyles[variant].selectedButton ?? styles.selectedButton),
|
style={({ focused }) => [
|
||||||
focused && (variantStyles[variant].focusedButton ?? styles.focusedButton),
|
styles.button,
|
||||||
style,
|
variantStyles[variant].button,
|
||||||
]}
|
isSelected && (variantStyles[variant].selectedButton ?? styles.selectedButton),
|
||||||
{...rest}
|
focused && (variantStyles[variant].focusedButton ?? styles.focusedButton),
|
||||||
>
|
]}
|
||||||
{text ? (
|
{...rest}
|
||||||
<ThemedText
|
>
|
||||||
style={[
|
{text ? (
|
||||||
styles.text,
|
<ThemedText
|
||||||
variantStyles[variant].text,
|
style={[
|
||||||
isSelected && (variantStyles[variant].selectedText ?? styles.selectedText),
|
styles.text,
|
||||||
textStyle,
|
variantStyles[variant].text,
|
||||||
]}
|
isSelected && (variantStyles[variant].selectedText ?? styles.selectedText),
|
||||||
>
|
textStyle,
|
||||||
{text}
|
]}
|
||||||
</ThemedText>
|
>
|
||||||
) : (
|
{text}
|
||||||
children
|
</ThemedText>
|
||||||
)}
|
) : (
|
||||||
</Pressable>
|
children
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
18
hooks/useButtonAnimation.ts
Normal file
18
hooks/useButtonAnimation.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
import { Animated } from 'react-native';
|
||||||
|
|
||||||
|
export const useButtonAnimation = (isSelected: boolean, isFocused: boolean) => {
|
||||||
|
const scaleValue = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Animated.spring(scaleValue, {
|
||||||
|
toValue: isSelected || isFocused ? 1.1 : 1,
|
||||||
|
friction: 5,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
}, [isSelected, isFocused, scaleValue]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
transform: [{ scale: scaleValue }],
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -11,7 +11,7 @@ export function useThemeColor(
|
|||||||
props: {light?: string; dark?: string},
|
props: {light?: string; dark?: string},
|
||||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
|
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
|
||||||
) {
|
) {
|
||||||
const theme = useColorScheme() ?? 'light';
|
const theme = useColorScheme() ?? 'dark';
|
||||||
const colorFromProps = props[theme];
|
const colorFromProps = props[theme];
|
||||||
|
|
||||||
if (colorFromProps) {
|
if (colorFromProps) {
|
||||||
|
|||||||
Reference in New Issue
Block a user