diff --git a/.gitignore b/.gitignore index f9070ace..c54f8060 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ web-build/ # Temporary files created by Metro to check the health of the file watcher .metro-health-check* -ios/ \ No newline at end of file +ios/ +android/ \ No newline at end of file diff --git a/app.config.ts b/app.config.ts index 8cc7ce56..383806fb 100644 --- a/app.config.ts +++ b/app.config.ts @@ -9,9 +9,7 @@ module.exports = { slug: "hydra", version: packageJson.version, newArchEnabled: true, - runtimeVersion: { - policy: 'appVersion', - }, + runtimeVersion: packageJson.version, orientation: "portrait", icon: "./assets/images/icon.png", scheme: "hydra", @@ -33,6 +31,7 @@ module.exports = { }, }, android: { + package: "com.dmilin.hydra", adaptiveIcon: { foregroundImage: "./assets/images/adaptive-icon.png", backgroundColor: "#000000" diff --git a/app/index.tsx b/app/index.tsx index 021084a1..04edea70 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -11,7 +11,8 @@ import { registerRootComponent } from "expo"; import { useFonts } from "expo-font"; import { SplashScreen } from "expo-router"; import React, { useEffect, useState } from "react"; -import { AppState, LogBox } from "react-native"; +import { AppState, LogBox, Platform } from "react-native"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { enableFreeze } from "react-native-screens"; @@ -59,7 +60,9 @@ Sentry.init({ SplashScreen.preventAutoHideAsync(); // Tells non visble tabs and screens to not rerender in the background -enableFreeze(true); +if (Platform.OS !== "android") { + enableFreeze(true); +} function RootLayout() { const { success: migrationsComplete, error } = useMigrations(db, migrations); @@ -105,34 +108,36 @@ function RootLayout() { migrationsComplete && fontsLoaded && dbMaintenanceDone && ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) ); } diff --git a/components/Modals/QuickSubredditSearch.tsx b/components/Modals/QuickSubredditSearch.tsx index fda037da..cdf019ae 100644 --- a/components/Modals/QuickSubredditSearch.tsx +++ b/components/Modals/QuickSubredditSearch.tsx @@ -99,6 +99,7 @@ export default function QuickSubredditSearch({ return ( - + {isActionSheetShowing && ( + + )} {children} ); diff --git a/contexts/ModalContext.tsx b/contexts/ModalContext.tsx index 2f2ab6ed..94ac4675 100644 --- a/contexts/ModalContext.tsx +++ b/contexts/ModalContext.tsx @@ -1,12 +1,5 @@ -import { - ReactNode, - createContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { Animated, StyleSheet, useWindowDimensions } from "react-native"; +import { ReactNode, createContext, useMemo, useState } from "react"; +import { StyleSheet, View } from "react-native"; type ModalContextType = { setModal: (modal?: ReactNode) => void; @@ -20,16 +13,6 @@ export const ModalContext = createContext(initialModalContext); export function ModalProvider({ children }: React.PropsWithChildren) { const [modal, setModal] = useState(null); - const { height } = useWindowDimensions(); - const modalPosition = useRef(new Animated.Value(height)).current; - - useEffect(() => { - Animated.spring(modalPosition, { - toValue: modal ? 0 : height, - bounciness: 2, - useNativeDriver: true, - }).start(); - }, [modal]); /** * Since this provider only provides functions, we need to memoize the value @@ -44,28 +27,19 @@ export function ModalProvider({ children }: React.PropsWithChildren) { return ( - - {modal} - {children} + {modal && {modal}} ); } const styles = StyleSheet.create({ modalContainer: { - position: "relative", + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, zIndex: 1000, }, }); diff --git a/contexts/StackFutureContext.tsx b/contexts/StackFutureContext.tsx index 35a79aff..514c80b4 100644 --- a/contexts/StackFutureContext.tsx +++ b/contexts/StackFutureContext.tsx @@ -16,7 +16,7 @@ import { useEffect, useRef, } from "react"; -import { Animated, useWindowDimensions } from "react-native"; +import { Animated, Platform, useWindowDimensions } from "react-native"; import { StackParamsList } from "../app/stack"; @@ -66,46 +66,50 @@ export function StackFutureProvider({ clearFuture: () => (futureRoutes.current = []), }} > - { - gestureStart.current = { - x: e.nativeEvent.pageX, - y: e.nativeEvent.pageY, - }; - return false; - }} - onMoveShouldSetResponder={() => - !!gestureStart.current && - width - gestureStart.current.x < 30 && - futureRoutes.current.length > 0 - } - onResponderMove={(e) => { - if (!gestureStart.current) return false; - const deltaX = gestureStart.current.x - e.nativeEvent.pageX; - const deltaY = e.nativeEvent.pageY - gestureStart.current.y; - const angleDegrees = Math.abs( - Math.atan2(deltaY, deltaX) * (180 / Math.PI), - ); - if (angleDegrees > 15 && deltaY > 30) { - gestureStart.current = null; + {Platform.OS === "android" ? ( + children + ) : ( + { + gestureStart.current = { + x: e.nativeEvent.pageX, + y: e.nativeEvent.pageY, + }; return false; + }} + onMoveShouldSetResponder={() => + !!gestureStart.current && + width - gestureStart.current.x < 30 && + futureRoutes.current.length > 0 } - if (deltaX > 15 && angleDegrees < 15) { - const popped = futureRoutes.current.pop(); - if (!popped) return false; - navigation.push(popped.name as any, popped.params as any); - gestureStart.current = null; - return true; - } - return false; - }} - > - {children} - + onResponderMove={(e) => { + if (!gestureStart.current) return false; + const deltaX = gestureStart.current.x - e.nativeEvent.pageX; + const deltaY = e.nativeEvent.pageY - gestureStart.current.y; + const angleDegrees = Math.abs( + Math.atan2(deltaY, deltaX) * (180 / Math.PI), + ); + if (angleDegrees > 15 && deltaY > 30) { + gestureStart.current = null; + return false; + } + if (deltaX > 15 && angleDegrees < 15) { + const popped = futureRoutes.current.pop(); + if (!popped) return false; + navigation.push(popped.name as any, popped.params as any); + gestureStart.current = null; + return true; + } + return false; + }} + > + {children} + + )} ); } diff --git a/package-lock.json b/package-lock.json index 516cd0a1..7567c504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hydra", - "version": "3.1.0", + "version": "3.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hydra", - "version": "3.1.0", + "version": "3.3.0", "dependencies": { "@expo/react-native-action-sheet": "^4.1.1", "@expo/vector-icons": "^15.0.3", @@ -50,10 +50,11 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", - "react-native-mmkv": "^3.3.0", + "react-native-gesture-handler": "~2.28.0", + "react-native-mmkv": "3.3.3", "react-native-purchases": "^8.9.3", "react-native-safe-area-context": "5.6.0", - "react-native-screens": "4.19.0-nightly-20251205-910978b9a", + "react-native-screens": "4.24.0", "react-native-url-polyfill": "^2.0.0", "react-native-web": "^0.21.0", "react-native-webview": "13.15.0", @@ -1581,6 +1582,18 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", @@ -4137,6 +4150,12 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -12355,6 +12374,21 @@ } } }, + "node_modules/react-native-gesture-handler": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", + "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", @@ -12366,9 +12400,9 @@ } }, "node_modules/react-native-mmkv": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-native-mmkv/-/react-native-mmkv-3.3.0.tgz", - "integrity": "sha512-2iPjIJ+IAODXN35wm53EN6nv+hR/0NXLLiLWOdPA/0gXDB2dYVrr6MqvoiFpxhLhdj0B8fkf5ARNVavJaSvuuQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/react-native-mmkv/-/react-native-mmkv-3.3.3.tgz", + "integrity": "sha512-GMsfOmNzx0p5+CtrCFRVtpOOMYNJXuksBVARSQrCFaZwjUyHJdQzcN900GGaFFNTxw2fs8s5Xje//RDKj9+PZA==", "license": "(MIT AND BSD-3-Clause)", "peerDependencies": { "react": "*", @@ -12403,9 +12437,9 @@ } }, "node_modules/react-native-screens": { - "version": "4.19.0-nightly-20251205-910978b9a", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.19.0-nightly-20251205-910978b9a.tgz", - "integrity": "sha512-5q3cpW1xqulUqVTJQsF/thPkoh7S6egxvRapypKWEVqwYr5FKIIakzjaGdtIlZ8o925XYtoUAk1qnDgBXM/2TA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.24.0.tgz", + "integrity": "sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==", "license": "MIT", "dependencies": { "react-freeze": "^1.0.0", diff --git a/package.json b/package.json index 5754d312..a50cae5b 100644 --- a/package.json +++ b/package.json @@ -58,10 +58,11 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.5", - "react-native-mmkv": "^3.3.0", + "react-native-gesture-handler": "~2.28.0", + "react-native-mmkv": "3.3.3", "react-native-purchases": "^8.9.3", "react-native-safe-area-context": "5.6.0", - "react-native-screens": "4.19.0-nightly-20251205-910978b9a", + "react-native-screens": "4.24.0", "react-native-url-polyfill": "^2.0.0", "react-native-web": "^0.21.0", "react-native-webview": "13.15.0", @@ -83,7 +84,7 @@ "wrapper-webpack-plugin": "^2.2.2" }, "overrides": { - "react-native-screens": "4.19.0-nightly-20251205-910978b9a" + "react-native-screens": "4.24.0" }, "private": true -} \ No newline at end of file +}