Skip to content

Commit

Permalink
feat: view file, image and video
Browse files Browse the repository at this point in the history
  • Loading branch information
TITANiumRox committed Dec 27, 2024
1 parent 4d934a5 commit a4858bb
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,48 @@
import ShareButton from "@components/common/ShareButton"
import VideoPlayer from "@components/features/video/VideoPlayer"
import useFileURL from "@hooks/useFileURL"
import { getFileExtension, isVideoFile } from "@raven/lib/utils/operations"
import { getFileExtension, isImageFile, isVideoFile } from "@raven/lib/utils/operations"
import { Stack, useLocalSearchParams } from "expo-router"
import { useState } from "react"
import WebView from "react-native-webview"
import { WebViewSourceUri } from "react-native-webview/lib/WebViewTypes"
import ImageViewer from "@components/features/image/ImageViewer"

const FileViewer = () => {

const { uri } = useLocalSearchParams() as { uri: string }

const [showHeader, setShowHeader] = useState(true)

const fileExtension = getFileExtension(uri)

const isVideo = isVideoFile(fileExtension)

const isImage = isImageFile(fileExtension)

const handleShowHeader = () => {
setShowHeader((prev) => !prev)
}

const renderFile = () => {
if (isVideo) {
return <VideoPlayer uri={uri} />
}
else if (isImage) {
return <ImageViewer uri={uri} handleShowHeader={handleShowHeader} />
}
return <FileView uri={uri} />
}

return (
<>
<Stack.Screen options={{
title: 'Raven',
headerShown: showHeader,
headerTitle: `${uri?.split('/').pop()}`,
headerRight: () => <ShareButton uri={uri} />
}} />
{isVideo ?
<VideoPlayer uri={uri} />
:
<FileView uri={uri} />
}
{renderFile()}
</>
)
}
Expand All @@ -42,4 +58,5 @@ const FileView = ({ uri }: { uri: string }) => {
)
}


export default FileViewer
38 changes: 38 additions & 0 deletions apps/mobile/components/common/ErrorBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { View, Text } from 'react-native';

interface BaseProps {
heading?: string
}

type ErrorBannerProps =
| (BaseProps & { message: string; error?: never })
| (BaseProps & { error: Error; message?: never })

const ErrorBanner = ({ heading, message, error }: ErrorBannerProps) => {
return (
<View className="container">
<View className="border-l-[6px] border-error-border bg-error-background flex w-full rounded-lg px-6 py-3 md:p-9">
<View className="w-full">
{(heading || error) && (heading ?
<Text className="mb-3 text-lg font-semibold text-error-heading">
{heading}
</Text> :
<Text className="mb-3 text-lg font-semibold text-error-heading">
{error?.name}
</Text>
)}
{(message || error) && (message ?
<Text className="text-error mb-2">
{message}
</Text> :
<Text className="text-error mb-2">
{error?.message}
</Text>
)}
</View>
</View>
</View>
)
}

export default ErrorBanner
48 changes: 48 additions & 0 deletions apps/mobile/components/features/image/ImageViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import ErrorBanner from "@components/common/ErrorBanner";
import useFileURL from "@hooks/useFileURL";
import { useWindowDimensions, Image, View } from "react-native";
import { fitContainer, ResumableZoom, Source, useImageResolution } from "react-native-zoom-toolkit";

interface ImageViewerProps {
uri: string
handleShowHeader: VoidFunction
}

const ImageViewer = ({ uri, handleShowHeader }: ImageViewerProps) => {
console.log('ImageViewer', uri)
const source = useFileURL(uri)
if (!source) {
return <View className="p-2">
<ErrorBanner message="Something went wrong" heading="Couldn't open image" />
</View>
}
return (
<ImageComponent source={source} handleShowHeader={handleShowHeader} />
)
}

export default ImageViewer

const ImageComponent = ({ source, handleShowHeader }: { source: Source, handleShowHeader: VoidFunction }) => {

const { width, height } = useWindowDimensions()
const { isFetching, resolution, error } = useImageResolution(source)
if (isFetching || resolution === undefined || error) {
console.log('isFetching', isFetching)
console.log('resolution', resolution)
return <View className="p-2">
{error && <ErrorBanner error={error} />}
</View>
}

const size = fitContainer(resolution.width / resolution.height, {
width,
height,
})

return (
<ResumableZoom maxScale={resolution} onTap={handleShowHeader}>
<Image source={source} style={{ ...size }} resizeMethod={'scale'} />
</ResumableZoom>
)
}
16 changes: 16 additions & 0 deletions apps/mobile/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
--accent-foreground: 255 255 255;
--destructive: 255 56 43;
--destructive-foreground: 255 255 255;
--error: 206 32 33;
--error-background: 251 234 233;
--error-heading: 176 20 20;
--error-border: 182 22 22;
--border: 230 230 235;
--input: 210 210 215;
--ring: 230 230 235;
Expand All @@ -40,6 +44,10 @@
--android-accent-foreground: 255 255 255;
--android-destructive: 186 26 26;
--android-destructive-foreground: 255 255 255;
--android-error: 206 32 33;
--android-error-background: 251 234 233;
--android-error-heading: 176 20 20;
--android-error-border: 182 22 22;
--android-border: 215 217 228;
--android-input: 210 210 215;
--android-ring: 215 217 228;
Expand All @@ -63,6 +71,10 @@
--accent-foreground: 255 255 255;
--destructive: 254 67 54;
--destructive-foreground: 255 255 255;
--error: 176 20 20;
--error-background: 39 19 20;
--error-heading: 220 50 50;
--error-border: 145 22 22;
--border: 40 40 42;
--input: 55 55 57;
--ring: 40 40 42;
Expand All @@ -83,6 +95,10 @@
--android-accent-foreground: 238 177 255;
--android-destructive: 147 0 10;
--android-destructive-foreground: 255 255 255;
--android-error: 176 20 20;
--android-error-background: 39 19 20;
--android-error-heading: 220 50 50;
--android-error-border: 145 22 22;
--android-border: 39 42 50;
--android-input: 55 55 57;
--android-ring: 39 42 50;
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/hooks/useFileURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type UseFileURLReturnType = {
* resource (which should be wrapped in the `require('./path/to/image.png')`
* function).
*/
uri: string | undefined;
uri: string;
/**
* `headers` is an object representing the HTTP headers to send along with the
* request for a remote image.
Expand Down
6 changes: 4 additions & 2 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,20 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.5",
"react-native-gesture-handler": "~2.20.2",
"react-native-gesture-handler": "^2.21.2",
"react-native-image-viewing": "^0.2.2",
"react-native-ios-context-menu": "^2.5.3",
"react-native-ios-utilities": "^4.5.1",
"react-native-keyboard-controller": "^1.15.0",
"react-native-reanimated": "~3.16.1",
"react-native-reanimated": "^3.16.6",
"react-native-safe-area-context": "^4.14.1",
"react-native-screens": "~4.1.0",
"react-native-svg": "^15.10.1",
"react-native-svg-transformer": "^1.5.0",
"react-native-uitextview": "^1.4.0",
"react-native-web": "~0.19.13",
"react-native-webview": "^13.12.5",
"react-native-zoom-toolkit": "^4.0.0",
"sonner-native": "^0.16.2",
"tailwind-merge": "^2.5.5"
},
Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ module.exports = {
DEFAULT: withOpacity('destructive'),
foreground: withOpacity('destructive-foreground'),
},
error: {
DEFAULT: withOpacity('error'),
background: withOpacity('error-background'),
heading: withOpacity('error-heading'),
border: withOpacity('error-border'),
},
muted: {
DEFAULT: withOpacity('muted'),
foreground: withOpacity('muted-foreground'),
Expand Down
Loading

0 comments on commit a4858bb

Please sign in to comment.