add header and background design

This commit is contained in:
Jorrin
2024-04-01 21:59:03 +02:00
parent 9ace6afc9e
commit 908da0bd24
14 changed files with 1003 additions and 963 deletions

View File

@@ -28,11 +28,11 @@
"@react-native-anywhere/polyfill-base64": "0.0.1-alpha.0", "@react-native-anywhere/polyfill-base64": "0.0.1-alpha.0",
"@react-navigation/native": "^6.1.9", "@react-navigation/native": "^6.1.9",
"@salihgun/react-native-video-processor": "^0.3.1", "@salihgun/react-native-video-processor": "^0.3.1",
"@tamagui/animations-moti": "^1.91.4", "@tamagui/animations-moti": "^1.94.0",
"@tamagui/babel-plugin": "^1.91.4", "@tamagui/babel-plugin": "^1.94.0",
"@tamagui/config": "^1.91.4", "@tamagui/config": "^1.94.0",
"@tamagui/metro-plugin": "^1.91.4", "@tamagui/metro-plugin": "^1.94.0",
"@tamagui/toast": "1.91.4", "@tamagui/toast": "1.94.0",
"@tanstack/react-query": "^5.22.2", "@tanstack/react-query": "^5.22.2",
"burnt": "^0.12.2", "burnt": "^0.12.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
@@ -75,7 +75,7 @@
"react-native-svg": "14.1.0", "react-native-svg": "14.1.0",
"react-native-web": "^0.19.10", "react-native-web": "^0.19.10",
"subsrt-ts": "^2.1.2", "subsrt-ts": "^2.1.2",
"tamagui": "^1.91.4", "tamagui": "^1.94.0",
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -16,7 +16,7 @@ import Item from "~/components/item/item";
import ScreenLayout from "~/components/layout/ScreenLayout"; import ScreenLayout from "~/components/layout/ScreenLayout";
import { SearchBar } from "~/components/ui/Searchbar"; import { SearchBar } from "~/components/ui/Searchbar";
export default function HomeScreen() { export default function SearchScreen() {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const translateY = useSharedValue(0); const translateY = useSharedValue(0);
const fadeAnim = useSharedValue(1); const fadeAnim = useSharedValue(1);
@@ -98,16 +98,16 @@ export default function HomeScreen() {
}; };
return ( return (
<View style={{ flex: 1 }}> <ScreenLayout>
<ScrollView <ScrollView
onScrollBeginDrag={handleScrollBegin} onScrollBeginDrag={handleScrollBegin}
onMomentumScrollEnd={handleScrollEnd} onMomentumScrollEnd={handleScrollEnd}
scrollEnabled={searchResultsLoaded ? true : false} scrollEnabled={searchResultsLoaded ? true : false}
keyboardDismissMode="on-drag" keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
contentContainerStyle={{ flexGrow: 1 }}
> >
<ScreenLayout> <View>
{searchResultsLoaded && (
<Animated.View style={[searchResultsStyle, { flex: 1 }]}> <Animated.View style={[searchResultsStyle, { flex: 1 }]}>
<View flexDirection="row" flexWrap="wrap"> <View flexDirection="row" flexWrap="wrap">
{data?.map((item, index) => ( {data?.map((item, index) => (
@@ -122,18 +122,22 @@ export default function HomeScreen() {
))} ))}
</View> </View>
</Animated.View> </Animated.View>
)} </View>
</ScreenLayout>
</ScrollView> </ScrollView>
<Animated.View <Animated.View
style={[ style={[
{ position: "absolute", left: 0, right: 0, bottom: 0 }, {
position: "absolute",
bottom: 0,
left: 0,
right: 0,
},
animatedStyle, animatedStyle,
]} ]}
> >
<SearchBar onSearchChange={setQuery} /> <SearchBar onSearchChange={setQuery} />
</Animated.View> </Animated.View>
</View> </ScreenLayout>
); );
} }

View File

@@ -0,0 +1,28 @@
import { Image, Text, View } from "tamagui";
import Icon from "../../assets/images/icon-transparent.png";
export function BrandPill() {
return (
<View
flexDirection="row"
alignItems="center"
justifyContent="center"
paddingHorizontal="$2.5"
paddingVertical="$2"
gap={2}
opacity={0.8}
backgroundColor="$pillBackground"
borderRadius={24}
pressStyle={{
opacity: 1,
scale: 1.05,
}}
>
<Image source={Icon} height={20} width={20} />
<Text fontSize="$4" fontWeight="$bold" paddingRight={5}>
movie-web
</Text>
</View>
);
}

View File

@@ -0,0 +1,41 @@
import { Linking } from "react-native";
import { FontAwesome6, MaterialIcons } from "@expo/vector-icons";
import { Circle, View } from "tamagui";
import { DISCORD_LINK, GITHUB_LINK } from "~/constants/core";
import { BrandPill } from "../BrandPill";
export function Header() {
return (
<View alignItems="center" gap="$3" flexDirection="row">
<BrandPill />
<Circle
backgroundColor="$pillBackground"
size="$2.5"
pressStyle={{
opacity: 1,
scale: 1.05,
}}
onPress={async () => {
await Linking.openURL(DISCORD_LINK);
}}
>
<MaterialIcons name="discord" size={20} color="white" />
</Circle>
<Circle
backgroundColor="$pillBackground"
size="$2.5"
pressStyle={{
opacity: 1,
scale: 1.05,
}}
onPress={async () => {
await Linking.openURL(GITHUB_LINK);
}}
>
<FontAwesome6 name="github" size={20} color="white" />
</Circle>
</View>
);
}

View File

@@ -1,24 +1,34 @@
import { Text, View } from "tamagui"; import { View } from "tamagui";
import { LinearGradient } from "tamagui/linear-gradient";
import { Header } from "./Header";
interface Props { interface Props {
title?: React.ReactNode | string;
subtitle?: string;
children?: React.ReactNode; children?: React.ReactNode;
} }
export default function ScreenLayout({ title, subtitle, children }: Props) { export default function ScreenLayout({ children }: Props) {
return ( return (
<View flex={1} padding={44} backgroundColor="$screenBackground"> <LinearGradient
{typeof title === "string" && ( flex={1}
<Text fontWeight="bold" fontSize={24}> paddingVertical="$4"
{title} paddingHorizontal="$7"
</Text> colors={[
)} "$shade900",
{typeof title !== "string" && title} "$purple900",
<Text fontSize={16} fontWeight="bold" marginTop={1}> "$purple800",
{subtitle} "$shade700",
</Text> "$shade900",
<View paddingVertical={12}>{children}</View> ]}
locations={[0.02, 0.15, 0.2, 0.4, 0.8]}
start={[0, 0]}
end={[1, 1]}
flexGrow={1}
>
<Header />
<View paddingVertical="$4" flexGrow={1}>
{children}
</View> </View>
</LinearGradient>
); );
} }

View File

@@ -1,7 +1,7 @@
import { Image, Text, View } from "tamagui"; import { Text, View } from "tamagui";
import { usePlayerStore } from "~/stores/player/store"; import { usePlayerStore } from "~/stores/player/store";
import Icon from "../../../assets/images/icon-transparent.png"; import { BrandPill } from "../BrandPill";
import { BackButton } from "./BackButton"; import { BackButton } from "./BackButton";
import { Controls } from "./Controls"; import { Controls } from "./Controls";
@@ -39,21 +39,8 @@ export const Header = () => {
: ""} : ""}
</Text> </Text>
)} )}
<View <View alignItems="center" justifyContent="center" width={130}>
height="$3.5" <BrandPill />
width="$11"
flexDirection="row"
alignItems="center"
justifyContent="center"
gap={2}
paddingHorizontal="$4"
paddingVertical="$1"
opacity={0.8}
backgroundColor="$pillBackground"
borderRadius={24}
>
<Image source={Icon} height={24} width={24} />
<Text fontWeight="bold">movie-web</Text>
</View> </View>
</View> </View>
); );

View File

@@ -28,7 +28,7 @@ export const QualitySelector = () => {
(key) => qualities[key as keyof typeof qualities]!.url === videoSrc.uri, (key) => qualities[key as keyof typeof qualities]!.url === videoSrc.uri,
); );
qualityMap = Object.keys(qualities).map((key: string) => ({ qualityMap = Object.keys(qualities).map((key) => ({
quality: key, quality: key,
url: qualities[key as keyof typeof qualities]!.url, url: qualities[key as keyof typeof qualities]!.url,
})); }));

View File

@@ -24,6 +24,7 @@ import { useScrape } from "~/hooks/player/useSourceScrape";
import { convertMetaToScrapeMedia } from "~/lib/meta"; import { convertMetaToScrapeMedia } from "~/lib/meta";
import { PlayerStatus } from "~/stores/player/slices/interface"; import { PlayerStatus } from "~/stores/player/slices/interface";
import { usePlayerStore } from "~/stores/player/store"; import { usePlayerStore } from "~/stores/player/store";
import { BackButton } from "./BackButton";
import { ScrapeCard, ScrapeItem } from "./ScrapeCard"; import { ScrapeCard, ScrapeItem } from "./ScrapeCard";
interface ScraperProcessProps { interface ScraperProcessProps {
@@ -169,6 +170,9 @@ export const ScraperProcess = ({
justifyContent="center" justifyContent="center"
backgroundColor="$screenBackground" backgroundColor="$screenBackground"
> >
<View position="absolute" top={40} left={40}>
<BackButton />
</View>
<ScrollView <ScrollView
ref={scrollViewRef} ref={scrollViewRef}
contentContainerStyle={{ contentContainerStyle={{

View File

@@ -17,7 +17,6 @@ export const MWInput = styled(Input, {
outlineStyle: "none", outlineStyle: "none",
focusStyle: { focusStyle: {
borderColor: "$colorTransparent", borderColor: "$colorTransparent",
backgroundColor: "$searchFocused",
}, },
}, },
}, },

View File

@@ -51,7 +51,6 @@ export function SearchBar({
return ( return (
<View <View
marginBottom={12}
flexDirection="row" flexDirection="row"
alignItems="center" alignItems="center"
borderRadius={999} borderRadius={999}
@@ -67,8 +66,7 @@ export function SearchBar({
onChangeText={handleChange} onChangeText={handleChange}
ref={inputRef} ref={inputRef}
placeholder="What are you looking for?" placeholder="What are you looking for?"
width="80%" width="75%"
borderColor={isFocused ? theme.colorTransparent : theme.inputBorder}
backgroundColor={ backgroundColor={
isFocused ? theme.searchFocused : theme.searchBackground isFocused ? theme.searchFocused : theme.searchBackground
} }

View File

@@ -0,0 +1,2 @@
export const DISCORD_LINK = "https://movie-web.github.io/links/discord";
export const GITHUB_LINK = "https://github.com/movie-web";

View File

@@ -21,7 +21,9 @@ export const useThemeStore = create(
updateTheme(newTheme); updateTheme(newTheme);
set((state) => { set((state) => {
state.theme = newTheme; state.theme = newTheme;
void setAlternateAppIcon(newTheme); setAlternateAppIcon(newTheme).catch(() => {
console.log("Failed to set alternate app icon");
});
}); });
}, },
}; };

View File

@@ -83,22 +83,97 @@ const createThemeConfig = (tokens: Tokens) => ({
inputPlaceholderText: tokens.shade.c300, inputPlaceholderText: tokens.shade.c300,
inputText: tokens.white, inputText: tokens.white,
inputIconColor: tokens.shade.c50, inputIconColor: tokens.shade.c50,
red100: tokens.semantic.red.c100,
red200: tokens.semantic.red.c200,
red300: tokens.semantic.red.c300,
red400: tokens.semantic.red.c400,
green100: tokens.semantic.green.c100,
green200: tokens.semantic.green.c200,
green300: tokens.semantic.green.c300,
green400: tokens.semantic.green.c400,
silver100: tokens.semantic.silver.c100,
silver200: tokens.semantic.silver.c200,
silver300: tokens.semantic.silver.c300,
silver400: tokens.semantic.silver.c400,
yellow100: tokens.semantic.yellow.c100,
yellow200: tokens.semantic.yellow.c200,
yellow300: tokens.semantic.yellow.c300,
yellow400: tokens.semantic.yellow.c400,
rose100: tokens.semantic.rose.c100,
rose200: tokens.semantic.rose.c200,
rose300: tokens.semantic.rose.c300,
rose400: tokens.semantic.rose.c400,
blue50: tokens.blue.c50,
blue100: tokens.blue.c100,
blue200: tokens.blue.c200,
blue300: tokens.blue.c300,
blue400: tokens.blue.c400,
blue500: tokens.blue.c500,
blue600: tokens.blue.c600,
blue700: tokens.blue.c700,
blue800: tokens.blue.c800,
blue900: tokens.blue.c900,
purple50: tokens.purple.c50,
purple100: tokens.purple.c100,
purple200: tokens.purple.c200,
purple300: tokens.purple.c300,
purple400: tokens.purple.c400,
purple500: tokens.purple.c500,
purple600: tokens.purple.c600,
purple700: tokens.purple.c700,
purple800: tokens.purple.c800,
purple900: tokens.purple.c900,
ash50: tokens.ash.c50,
ash100: tokens.ash.c100,
ash200: tokens.ash.c200,
ash300: tokens.ash.c300,
ash400: tokens.ash.c400,
ash500: tokens.ash.c500,
ash600: tokens.ash.c600,
ash700: tokens.ash.c700,
ash800: tokens.ash.c800,
ash900: tokens.ash.c900,
shade50: tokens.shade.c50,
shade100: tokens.shade.c100,
shade200: tokens.shade.c200,
shade300: tokens.shade.c300,
shade400: tokens.shade.c400,
shade500: tokens.shade.c500,
shade600: tokens.shade.c600,
shade700: tokens.shade.c700,
shade800: tokens.shade.c800,
shade900: tokens.shade.c900,
}); });
const openSansFace = { const openSansFace = {
normal: { normal: "OpenSans-Regular", italic: "OpenSans-Italic" }, normal: { normal: "OpenSansRegular", italic: "OpenSansItalic" },
bold: { normal: "OpenSans-Bold", italic: "OpenSans-BoldItalic" }, bold: { normal: "OpenSansBold", italic: "OpenSansBoldItalic" },
300: { normal: "OpenSans-Light", italic: "OpenSans-LightItalic" }, 300: { normal: "OpenSansLight", italic: "OpenSansLightItalic" },
500: { normal: "OpenSans-Regular", italic: "OpenSans-Italic" }, 500: { normal: "OpenSansRegular", italic: "OpenSansItalic" },
600: { normal: "OpenSans-SemiBold", italic: "OpenSans-SemiBoldItalic" }, 600: { normal: "OpenSansSemiBold", italic: "OpenSansSemiBoldItalic" },
700: { normal: "OpenSans-Bold", italic: "OpenSans-BoldItalic" }, 700: { normal: "OpenSansBold", italic: "OpenSansBoldItalic" },
800: { normal: "OpenSans-ExtraBold", italic: "OpenSans-ExtraBoldItalic" }, 800: { normal: "OpenSansExtraBold", italic: "OpenSansExtraBoldItalic" },
}; };
const headingFont = createFont({ const headingFont = createFont({
size: config.fonts.heading.size, size: config.fonts.heading.size,
lineHeight: config.fonts.heading.lineHeight, lineHeight: config.fonts.heading.lineHeight,
weight: config.fonts.heading.weight, weight: {
light: 300,
normal: 500,
semibold: 600,
bold: 700,
extrabold: 800,
},
letterSpacing: config.fonts.heading.letterSpacing, letterSpacing: config.fonts.heading.letterSpacing,
face: openSansFace, face: openSansFace,
}); });
@@ -106,7 +181,13 @@ const headingFont = createFont({
const bodyFont = createFont({ const bodyFont = createFont({
size: config.fonts.body.size, size: config.fonts.body.size,
lineHeight: config.fonts.body.lineHeight, lineHeight: config.fonts.body.lineHeight,
weight: config.fonts.body.weight, weight: {
light: 300,
normal: 500,
semibold: 600,
bold: 700,
extrabold: 800,
},
letterSpacing: config.fonts.body.letterSpacing, letterSpacing: config.fonts.body.letterSpacing,
face: openSansFace, face: openSansFace,
}); });

1658
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff