diff --git a/apps/mobile/app/file-viewer.tsx b/apps/mobile/app/[site_id]/chat/[id]/file-viewer.tsx similarity index 58% rename from apps/mobile/app/file-viewer.tsx rename to apps/mobile/app/[site_id]/chat/[id]/file-viewer.tsx index 9f055f6d..44fde45d 100644 --- a/apps/mobile/app/file-viewer.tsx +++ b/apps/mobile/app/[site_id]/chat/[id]/file-viewer.tsx @@ -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 + } + else if (isImage) { + return + } + return + } return ( <> }} /> - {isVideo ? - - : - - } + {renderFile()} ) } @@ -42,4 +58,5 @@ const FileView = ({ uri }: { uri: string }) => { ) } + export default FileViewer \ No newline at end of file diff --git a/apps/mobile/components/common/ErrorBanner.tsx b/apps/mobile/components/common/ErrorBanner.tsx new file mode 100644 index 00000000..b928a89c --- /dev/null +++ b/apps/mobile/components/common/ErrorBanner.tsx @@ -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 ( + + + + {(heading || error) && (heading ? + + {heading} + : + + {error?.name} + + )} + {(message || error) && (message ? + + {message} + : + + {error?.message} + + )} + + + + ) +} + +export default ErrorBanner diff --git a/apps/mobile/components/features/image/ImageViewer.tsx b/apps/mobile/components/features/image/ImageViewer.tsx new file mode 100644 index 00000000..256a8611 --- /dev/null +++ b/apps/mobile/components/features/image/ImageViewer.tsx @@ -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 + + + } + return ( + + ) +} + +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 + {error && } + + } + + const size = fitContainer(resolution.width / resolution.height, { + width, + height, + }) + + return ( + + + + ) +} diff --git a/apps/mobile/global.css b/apps/mobile/global.css index c5deaa2e..c120c7fe 100644 --- a/apps/mobile/global.css +++ b/apps/mobile/global.css @@ -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; @@ -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; @@ -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; @@ -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; diff --git a/apps/mobile/hooks/useFileURL.ts b/apps/mobile/hooks/useFileURL.ts index 178521fe..2efb54d0 100644 --- a/apps/mobile/hooks/useFileURL.ts +++ b/apps/mobile/hooks/useFileURL.ts @@ -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. diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 64b097bb..70962d18 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -47,11 +47,12 @@ "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", @@ -59,6 +60,7 @@ "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" }, diff --git a/apps/mobile/tailwind.config.js b/apps/mobile/tailwind.config.js index b7607236..7622441e 100644 --- a/apps/mobile/tailwind.config.js +++ b/apps/mobile/tailwind.config.js @@ -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'), diff --git a/yarn.lock b/yarn.lock index 741bc5f0..94918c90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1100,20 +1100,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": - version "7.26.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" - integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.3" - "@babel/parser" "^7.26.3" - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.3" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.23.0", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.23.0", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": version "7.26.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== @@ -4839,7 +4826,7 @@ ci-info@^3.2.0, ci-info@^3.3.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -class-variance-authority@^0.7.1: +class-variance-authority@^0.7.1, "cva@npm:class-variance-authority": version "0.7.1" resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787" integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== @@ -5202,13 +5189,6 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -"cva@npm:class-variance-authority": - version "0.7.1" - resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787" - integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== - dependencies: - clsx "^2.1.1" - data-uri-to-buffer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" @@ -5896,6 +5876,11 @@ expo-secure-store@~14.0.0: resolved "https://registry.yarnpkg.com/expo-secure-store/-/expo-secure-store-14.0.0.tgz#cf6eb7f73e619f8907d5a073e2f438927b5fc2ab" integrity sha512-VyhtRFXP+7hQmHhKlHIOWid1Q/IRpM7Uif32tZHLZHvQ6FNz2cUkr26XWGvCa7btYbrR6OL++FBFZYjbIcRZTw== +expo-sharing@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f" + integrity sha512-b23ymicRmYn/Pjj05sl9tFZHN5cH9I1f0yiqY1Yk8Q3oCx0Aznri82DnTYA4T/J6D9vrkraX0wQ4jWVMOffmlg== + expo-status-bar@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.0.tgz#dd99adc2ace12a24c92718cd0f97b93347103393" @@ -5909,6 +5894,11 @@ expo-system-ui@~4.0.6: "@react-native/normalize-colors" "0.76.5" debug "^4.3.2" +expo-video@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-2.0.3.tgz#22ef37f6323b20fb2d2f7e7a4684977bbe98baab" + integrity sha512-cBY6fBVj+jUXrKw7Xfn4/QwpXKMKPdv/4LCnFHy/SjR+suG4jIHMsCM2eZgqxXF1cbA8CII5LmK6LwmKB7DjMw== + expo-web-browser@^14.0.1, expo-web-browser@~14.0.0: version "14.0.1" resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.1.tgz#97f3f141b0897364bc8364d90d6e29df0beec8aa" @@ -6697,7 +6687,7 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" -invariant@^2.2.4: +invariant@2.2.4, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -8932,10 +8922,10 @@ react-native-drawer-layout@^4.1.1: dependencies: use-latest-callback "^0.2.1" -react-native-gesture-handler@~2.20.2: - version "2.20.2" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz#73844c8e9c417459c2f2981bc4d8f66ba8a5ee66" - integrity sha512-HqzFpFczV4qCnwKlvSAvpzEXisL+Z9fsR08YV5LfJDkzuArMhBu2sOoSPUF/K62PCoAb+ObGlTC83TKHfUd0vg== +react-native-gesture-handler@^2.21.2: + version "2.21.2" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.21.2.tgz#824a098d7397212fbe51aa3a9df84833a4ea1c3f" + integrity sha512-HcwB225K9aeZ8e/B8nFzEh+2T4EPWTeamO1l/y3PcQ9cyCDYO2zja/G31ITpYRIqkip7XzGs6wI/gnHOQn1LDQ== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" @@ -8951,6 +8941,11 @@ react-native-helmet-async@2.0.4: react-fast-compare "^3.2.2" shallowequal "^1.1.0" +react-native-image-viewing@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/react-native-image-viewing/-/react-native-image-viewing-0.2.2.tgz#fb26e57d7d3d9ce4559a3af3d244387c0367242b" + integrity sha512-osWieG+p/d2NPbAyonOMubttajtYEYiRGQaJA54slFxZ69j1V4/dCmcrVQry47ktVKy8/qpFwCpW1eT6MH5T2Q== + react-native-ios-context-menu@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-2.5.3.tgz#3e65a6cee50e95a71766ad3ebc3920015eb02318" @@ -8975,10 +8970,10 @@ react-native-keyboard-controller@^1.15.0: dependencies: react-native-is-edge-to-edge "^1.1.6" -react-native-reanimated@~3.16.1: - version "3.16.5" - resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.16.5.tgz#2a411b9030a8659722a9398d2e0ea19bc076c846" - integrity sha512-mq/5k14pimkhCeP9XwFJkEr8XufaHqIekum8fqpsn0fcBzbLvyiqfM2LEuBvi0+DTv5Bd2dHmUHkYqGYfkj3Jw== +react-native-reanimated@^3.16.6: + version "3.16.6" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.16.6.tgz#fa1eda23b740c893e81a024712346c79f1bbdf36" + integrity sha512-jPbAfLF5t8+UCKFTO+LeOY+OmAcDP5SsAfqINvNQz5GFGvoO7UebxujjtY58CmpZNH6c3SQ514FF9//mZDpo/g== dependencies: "@babel/plugin-transform-arrow-functions" "^7.0.0-0" "@babel/plugin-transform-class-properties" "^7.0.0-0" @@ -9043,6 +9038,19 @@ react-native-web@~0.19.13: postcss-value-parser "^4.2.0" styleq "^0.1.3" +react-native-webview@^13.12.5: + version "13.12.5" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-13.12.5.tgz#ed9eec1eda234d7cf18d329859b9bdebf7e258b6" + integrity sha512-INOKPom4dFyzkbxbkuQNfeRG9/iYnyRDzrDkJeyvSWgJAW2IDdJkWFJBS2v0RxIL4gqLgHkiIZDOfiLaNnw83Q== + dependencies: + escape-string-regexp "^4.0.0" + invariant "2.2.4" + +react-native-zoom-toolkit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-native-zoom-toolkit/-/react-native-zoom-toolkit-4.0.0.tgz#f609b282123bb88f08c5d7a37ba139e568f9fd97" + integrity sha512-xn1p4CCiAqae8ZV8AjxfQa9fxct/GYq3iLSq6SkvamiBSxXX/ZcP8EvwPX5ROOhgcoe6R9Pt+7TGMIdK1H1YjA== + react-native@0.76.5: version "0.76.5" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.76.5.tgz#3ce43d3c31f46cfd98e56ef2dfc70866c04ad185" @@ -9763,6 +9771,11 @@ socket.io-parser@~4.2.4: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" +sonner-native@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/sonner-native/-/sonner-native-0.16.2.tgz#046fd9c750c08f51546bb2b9b53fc940f8c7bc7d" + integrity sha512-IxiqcwZ2ZqmbtMWWhFq/um31hQndEUOa9ERnhS7+Z7lc0v9b9boa9pjHtNRoDktJ/OzEOMb1ERyr2bCNYNdUog== + sonner@^1.7.0: version "1.7.1" resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.7.1.tgz#737110a3e6211d8d766442076f852ddde1725205" @@ -9876,16 +9889,7 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9970,7 +9974,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9984,13 +9988,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -11020,16 +11017,7 @@ workbox-window@7.3.0, workbox-window@^7.1.0: "@types/trusted-types" "^2.0.2" workbox-core "7.3.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==