Skip to content

Commit

Permalink
Init repo
Browse files Browse the repository at this point in the history
  • Loading branch information
tinncdev committed May 24, 2024
0 parents commit 94a3a09
Show file tree
Hide file tree
Showing 43 changed files with 2,041 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/

# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# react-native-pip-reanimated Example

Make from [React Native Reusables starter base template](https://github.com/mrzachnugent/react-native-reusables) and [react-native-pip-reanimated example](https://github.com/meanguppy/react-native-pip-reanimated/tree/main/example) with some small modifications.

<image src="./assets/previews/preview.gif" alt="react-native-pip-reanimated example app" style="width:320px;" />

_For more detailed screen record, go to source code `/assets/preview/preview.mov` _
51 changes: 51 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"expo": {
"name": "Starter Base",
"slug": "starter-base",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-image-picker",
{
"photosPermission": "Allow $(PRODUCT_NAME) accesses your photos to let you share them with your friends.",
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone"
}
],
[
"expo-document-picker",
{
"iCloudContainerEnvironment": "Production"
}
]
],
"experiments": {
"typedRoutes": true
}
}
}
87 changes: 87 additions & 0 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import "~/global.css";

import AsyncStorage from "@react-native-async-storage/async-storage";
import { Theme, ThemeProvider } from "@react-navigation/native";
import { SplashScreen, Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import * as React from "react";
import { Platform } from "react-native";
import { NAV_THEME } from "~/lib/constants";
import { useColorScheme } from "~/lib/useColorScheme";
import { PortalHost } from "~/components/primitives/portal";
import { ThemeToggle } from "~/components/ThemeToggle";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { SafeAreaProvider } from "react-native-safe-area-context";

const LIGHT_THEME: Theme = {
dark: false,
colors: NAV_THEME.light,
};
const DARK_THEME: Theme = {
dark: true,
colors: NAV_THEME.dark,
};

export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from "expo-router";

// Prevent the splash screen from auto-hiding before getting the color scheme.
SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
const { colorScheme, setColorScheme, isDarkColorScheme } = useColorScheme();
const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);

React.useEffect(() => {
(async () => {
const theme = await AsyncStorage.getItem("theme");
if (Platform.OS === "web") {
// Adds the background color to the html element to prevent white background on overscroll.
document.documentElement.classList.add("bg-background");
}
if (!theme) {
AsyncStorage.setItem("theme", colorScheme);
setIsColorSchemeLoaded(true);
return;
}
const colorTheme = theme === "dark" ? "dark" : "light";
if (colorTheme !== colorScheme) {
setColorScheme(colorTheme);

setIsColorSchemeLoaded(true);
return;
}
setIsColorSchemeLoaded(true);
})().finally(() => {
SplashScreen.hideAsync();
});
}, []);

if (!isColorSchemeLoaded) {
return null;
}

return (
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<ActionSheetProvider>
<SafeAreaProvider>
<>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<Stack>
<Stack.Screen
name="index"
options={{
title: "Starter Base",
headerRight: () => <ThemeToggle />,
}}
/>
</Stack>
<PortalHost />
</>
</SafeAreaProvider>
</ActionSheetProvider>
</ThemeProvider>
);
}
140 changes: 140 additions & 0 deletions app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useState } from "react";
import { SafeAreaView, View } from "react-native";
import { Button } from "~/components/primitives/button";
import {
EdgeConfig,
PictureInPictureView,
ToggleableOverlay,
} from "react-native-pip-reanimated";
import { Text } from "~/components/primitives/text";
import {
GestureHandlerRootView,
TouchableOpacity,
} from "react-native-gesture-handler";
import { cssInterop } from "nativewind";
import {
PlayCircleIcon,
SkipBackIcon,
SkipForwardIcon,
} from "lucide-react-native";

const StyledPictureInPictureView = cssInterop(PictureInPictureView, {
className: "style",
});

const StyledToggleableOverlay = cssInterop(ToggleableOverlay, {
className: "style",
});

function ButtonControls() {
return (
<View className="w-full h-full flex items-center justify-center">
<View className="flex flex-row gap-2">
<TouchableOpacity
onPress={() => {
console.log("Button clicked", Date.now());
}}
>
<Button className="rounded-full" variant="outline" size="icon">
<SkipBackIcon size={16} />
</Button>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
console.log("Play", Date.now());
}}
>
<Button className="rounded-full" variant="outline" size="icon">
<PlayCircleIcon size={16} />
</Button>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
console.log("Next", Date.now());
}}
>
<Button className="rounded-full" variant="outline" size="icon">
<SkipForwardIcon size={16} />
</Button>
</TouchableOpacity>
</View>
</View>
);
}

export default function Screen() {
const [isPiPVisible, setPiPVisible] = useState<boolean>(false);

const onPressShowPiP = () => {
setPiPVisible(true);
};

const onDissmissPiP = () => {
setPiPVisible(false);
};

return (
<GestureHandlerRootView className="flex-1">
<SafeAreaView className="flex-1">
<View className="flex-1 justify-center items-center gap-4">
<Button onPress={onPressShowPiP}>
<Text>Show PiP</Text>
</Button>

{isPiPVisible && (
<StyledPictureInPictureView
className="w-1/2 border rounded-lg aspect-video shadow-lg bg-muted"
edgeConfig={{
top: PIP_VERTICAL_EDGE_CONFIG,
bottom: PIP_VERTICAL_EDGE_CONFIG,
left: PIP_HORIZONTAL_EDGE_CONFIG,
right: PIP_HORIZONTAL_EDGE_CONFIG,
}}
initialPosition="bottom-right"
deceleration={0.985}
minimumGlideVelocity={120}
destroyOverlayColor="rgba(255,0,0,0.5)"
onDestroy={onDissmissPiP}
>
<View className="p-4">
<Text>Some PiP content</Text>
</View>
<StyledToggleableOverlay className="bg-foreground/20">
<ButtonControls />
</StyledToggleableOverlay>
</StyledPictureInPictureView>
)}
</View>
</SafeAreaView>
</GestureHandlerRootView>
);
}

const PIP_VERTICAL_EDGE_CONFIG: EdgeConfig = {
margin: 8,
spring: {
stiffness: 600,
damping: 15,
mass: 0.2,
},
resistance: 0.8,
};

const PIP_HORIZONTAL_EDGE_CONFIG: EdgeConfig = {
margin: 8,
spring: {
stiffness: 500,
damping: 40,
mass: 0.8,
},
destroyByFling: {
minimumVelocity: 2400,
maximumAngle: 30 * (Math.PI / 180),
lockAxis: true,
fadeDuration: 200,
},
destroyByDrag: {
minimumOutOfBounds: 0.5,
animateVelocity: 1000,
},
};
Binary file added assets/images/adaptive-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/previews/preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/previews/preview.mov
Binary file not shown.
6 changes: 6 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
};
};
Binary file added bun.lockb
Binary file not shown.
21 changes: 21 additions & 0 deletions components/Icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Info, LucideIcon, MoonStar, Sun } from 'lucide-react-native';
import { cssInterop } from 'nativewind';

function interopIcon(icon: LucideIcon) {
cssInterop(icon, {
className: {
target: 'style',
nativeStyleToProp: {
color: true,
opacity: true,
},
},
});
}

interopIcon(Info);
interopIcon(MoonStar);
interopIcon(Sun);


export { Info, MoonStar, Sun };
36 changes: 36 additions & 0 deletions components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Pressable, View } from 'react-native';
import { MoonStar, Sun } from '~/components/Icons';
import { setAndroidNavigationBar } from '~/lib/android-navigation-bar';
import { useColorScheme } from '~/lib/useColorScheme';
import { cn } from '~/lib/utils';

export function ThemeToggle() {
const { isDarkColorScheme, setColorScheme } = useColorScheme();
return (
<Pressable
onPress={() => {
const newTheme = isDarkColorScheme ? 'light' : 'dark';
setColorScheme(newTheme);
setAndroidNavigationBar(newTheme);
AsyncStorage.setItem('theme', newTheme);
}}
className='web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2'
>
{({ pressed }) => (
<View
className={cn(
'flex-1 aspect-square pt-0.5 justify-center items-start web:px-5',
pressed && 'opacity-70'
)}
>
{isDarkColorScheme ? (
<MoonStar className='text-foreground' size={23} strokeWidth={1.25} />
) : (
<Sun className='text-foreground' size={24} strokeWidth={1.25} />
)}
</View>
)}
</Pressable>
);
}
Loading

0 comments on commit 94a3a09

Please sign in to comment.