From badae3c75af0a042fd0a4307f8e0771b339aed47 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:26:05 -0500 Subject: [PATCH] [MOB-37, MOB-38, MOB-39] Preview for PDF, Text and Media files (#2098) * version & microphonePermission text & eslint * remove polyfill as hermes supports intl now * why do we have solid on mobile? * cleanup * add solid back =_= * pnpm lock * we hate relative paths here * android config * open file logic * visual tweaks --------- Co-authored-by: ameer2468 <33054370+ameer2468@users.noreply.github.com> --- apps/mobile/app.json | 11 +- .../modules/sd-core/android/build.gradle | 4 +- apps/mobile/package.json | 163 +++++++++--------- apps/mobile/scripts/withAndroidIntent.js | 26 +++ apps/mobile/{ => scripts}/withRiveAssets.js | 0 apps/mobile/src/App.tsx | 6 +- .../src/components/explorer/Explorer.tsx | 6 +- apps/mobile/src/components/header/Header.tsx | 17 +- .../modal/inspector/ActionsModal.tsx | 41 ++++- apps/mobile/src/components/overview/Cloud.tsx | 33 ++-- .../src/components/overview/Locations.tsx | 4 +- apps/mobile/src/main.tsx | 14 -- apps/mobile/src/screens/Location.tsx | 12 +- apps/mobile/src/stores/explorerStore.ts | 7 +- .../Layout/Sidebar/JobManager/JobGroup.tsx | 4 +- packages/client/src/lib/explorerItem.ts | 6 +- pnpm-lock.yaml | Bin 960458 -> 960537 bytes 17 files changed, 202 insertions(+), 152 deletions(-) create mode 100644 apps/mobile/scripts/withAndroidIntent.js rename apps/mobile/{ => scripts}/withRiveAssets.js (100%) diff --git a/apps/mobile/app.json b/apps/mobile/app.json index d2df46af9..22b895f3a 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -3,7 +3,7 @@ "name": "Spacedrive", "slug": "spacedrive", "owner": "spacedrive", - "version": "0.0.1", + "version": "0.1.0", "orientation": "portrait", "jsEngine": "hermes", "scheme": "spacedrive", @@ -58,7 +58,14 @@ } } ], - ["./withRiveAssets.js"] + [ + "expo-av", + { + "microphonePermission": "Allow Spacedrive to access your microphone." + } + ], + ["./scripts/withRiveAssets.js"], + ["./scripts/withAndroidIntent.js"] ] } } diff --git a/apps/mobile/modules/sd-core/android/build.gradle b/apps/mobile/modules/sd-core/android/build.gradle index 6ea1daefd..cfb66dc11 100644 --- a/apps/mobile/modules/sd-core/android/build.gradle +++ b/apps/mobile/modules/sd-core/android/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'maven-publish' group = 'com.spacedrive.core' -version = '0.0.1' +version = '0.1.0' buildscript { def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") @@ -72,7 +72,7 @@ android { minSdkVersion safeExtGet("minSdkVersion", 28) targetSdkVersion safeExtGet("targetSdkVersion", 34) versionCode 1 - versionName "0.2.0" + versionName "0.1.0" } lintOptions { abortOnError false diff --git a/apps/mobile/package.json b/apps/mobile/package.json index bfe50570f..c6e0b210f 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,83 +1,84 @@ { - "name": "@sd/mobile", - "version": "1.0.0", - "main": "index.js", - "license": "GPL-3.0-only", - "private": true, - "scripts": { - "start": "expo start --dev-client", - "android": "expo run:android", - "ios": "expo run:ios", - "prebuild": "expo prebuild", - "xcode": "open ios/Spacedrive.xcworkspace", - "android-studio": "open -a '/Applications/Android Studio.app' ./android", - "lint": "eslint src --cache", - "test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios", - "export": "expo export", - "typecheck": "tsc -b" - }, - "dependencies": { - "@gorhom/bottom-sheet": "^4.4.7", - "@hookform/resolvers": "^3.1.0", - "@oscartbeaumont-sd/rspc-client": "=0.0.0-main-dc31e5b2", - "@oscartbeaumont-sd/rspc-react": "=0.0.0-main-dc31e5b2", - "@react-native-async-storage/async-storage": "~1.21.0", - "@react-native-masked-view/masked-view": "^0.3.0", - "@react-navigation/bottom-tabs": "^6.5.8", - "@react-navigation/drawer": "^6.6.3", - "@react-navigation/native": "^6.1.7", - "@react-navigation/stack": "^6.3.17", - "@sd/assets": "workspace:*", - "@sd/client": "workspace:*", - "@shopify/flash-list": "1.6.3", - "@tanstack/react-query": "^4.36.1", - "babel-preset-solid": "^1.8.9", - "class-variance-authority": "^0.7.0", - "dayjs": "^1.11.10", - "event-target-polyfill": "^0.0.3", - "expo": "~50.0.6", - "expo-av": "^13.10.5", - "expo-blur": "^12.9.1", - "expo-build-properties": "~0.11.1", - "expo-linking": "~6.2.2", - "expo-media-library": "~15.9.1", - "expo-splash-screen": "~0.26.4", - "expo-status-bar": "~1.11.1", - "intl": "^1.2.5", - "lottie-react-native": "6.5.1", - "metro-react-native-babel-transformer": "^0.77.0", - "moti": "^0.26.0", - "phosphor-react-native": "^2.0.0", - "react": "^18.2.0", - "react-hook-form": "^7.47.0", - "react-native": "0.73.4", - "react-native-circular-progress": "^1.3.9", - "react-native-document-picker": "^9.0.1", - "react-native-fs": "^2.20.0", - "react-native-gesture-handler": "~2.14.1", - "react-native-linear-gradient": "^2.8.3", - "react-native-popup-menu": "^0.16.1", - "react-native-reanimated": "~3.6.2", - "react-native-safe-area-context": "4.8.2", - "react-native-screens": "~3.29.0", - "react-native-svg": "14.1.0", - "react-native-wheel-color-picker": "^1.2.0", - "rive-react-native": "^6.2.3", - "solid-js": "^1.8.8", - "twrnc": "^3.6.4", - "use-count-up": "^3.0.1", - "use-debounce": "^9.0.4", - "valtio": "^1.11.2", - "zod": "~3.22.4" - }, - "devDependencies": { - "@babel/core": "^7.23.2", - "@rnx-kit/metro-config": "^1.3.12", - "@sd/config": "workspace:*", - "@types/react": "^18.2.52", - "babel-plugin-module-resolver": "^5.0.0", - "eslint-plugin-react-native": "^4.1.0", - "react-native-svg-transformer": "^1.1.0", - "typescript": "^5.3.3" - } + "name": "@sd/mobile", + "version": "1.0.0", + "main": "index.js", + "license": "GPL-3.0-only", + "private": true, + "scripts": { + "start": "expo start --dev-client", + "android": "expo run:android", + "ios": "expo run:ios", + "prebuild": "expo prebuild", + "xcode": "open ios/Spacedrive.xcworkspace", + "android-studio": "open -a '/Applications/Android Studio.app' ./android", + "lint": "eslint src --cache", + "test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios", + "export": "expo export", + "typecheck": "tsc -b", + "format": "prettier --write ." + }, + "dependencies": { + "@gorhom/bottom-sheet": "^4.4.7", + "@hookform/resolvers": "^3.1.0", + "@oscartbeaumont-sd/rspc-client": "=0.0.0-main-dc31e5b2", + "@oscartbeaumont-sd/rspc-react": "=0.0.0-main-dc31e5b2", + "@react-native-async-storage/async-storage": "~1.21.0", + "@react-native-masked-view/masked-view": "^0.3.0", + "@react-navigation/bottom-tabs": "^6.5.8", + "@react-navigation/drawer": "^6.6.3", + "@react-navigation/native": "^6.1.7", + "@react-navigation/stack": "^6.3.17", + "@sd/assets": "workspace:*", + "@sd/client": "workspace:*", + "@shopify/flash-list": "1.6.3", + "@tanstack/react-query": "^4.36.1", + "babel-preset-solid": "^1.8.9", + "class-variance-authority": "^0.7.0", + "dayjs": "^1.11.10", + "event-target-polyfill": "^0.0.3", + "expo": "~50.0.6", + "expo-av": "^13.10.5", + "expo-blur": "^12.9.1", + "expo-build-properties": "~0.11.1", + "expo-linking": "~6.2.2", + "expo-media-library": "~15.9.1", + "expo-splash-screen": "~0.26.4", + "expo-status-bar": "~1.11.1", + "lottie-react-native": "6.5.1", + "metro-react-native-babel-transformer": "^0.77.0", + "moti": "^0.26.0", + "phosphor-react-native": "^2.0.0", + "react": "^18.2.0", + "react-hook-form": "^7.47.0", + "react-native": "0.73.4", + "react-native-circular-progress": "^1.3.9", + "react-native-document-picker": "^9.0.1", + "react-native-file-viewer": "^2.1.5", + "react-native-fs": "^2.20.0", + "react-native-gesture-handler": "~2.14.1", + "react-native-linear-gradient": "^2.8.3", + "react-native-popup-menu": "^0.16.1", + "react-native-reanimated": "~3.6.2", + "react-native-safe-area-context": "4.8.2", + "react-native-screens": "~3.29.0", + "react-native-svg": "14.1.0", + "react-native-wheel-color-picker": "^1.2.0", + "rive-react-native": "^6.2.3", + "solid-js": "^1.8.8", + "twrnc": "^3.6.4", + "use-count-up": "^3.0.1", + "use-debounce": "^9.0.4", + "valtio": "^1.11.2", + "zod": "~3.22.4" + }, + "devDependencies": { + "@babel/core": "^7.23.2", + "@rnx-kit/metro-config": "^1.3.12", + "@sd/config": "workspace:*", + "@types/react": "^18.2.52", + "babel-plugin-module-resolver": "^5.0.0", + "eslint-plugin-react-native": "^4.1.0", + "react-native-svg-transformer": "^1.1.0", + "typescript": "^5.3.3" + } } diff --git a/apps/mobile/scripts/withAndroidIntent.js b/apps/mobile/scripts/withAndroidIntent.js new file mode 100644 index 000000000..b5651742b --- /dev/null +++ b/apps/mobile/scripts/withAndroidIntent.js @@ -0,0 +1,26 @@ +const { withAndroidManifest } = require('@expo/config-plugins'); + +// NOTE: Can be extended if needed (https://forums.expo.dev/t/how-to-edit-android-manifest-was-build/65663/4) +function modifyAndroidManifest(androidManifest) { + const { manifest } = androidManifest; + + const intent = manifest['queries'][0]['intent'][0]; + + if (intent) { + // Adds to the intents + intent['data'].push({ + $: { + 'android:mimeType': '*/*' + } + }); + } + + return androidManifest; +} + +module.exports = function withAndroidIntent(config) { + return withAndroidManifest(config, (config) => { + config.modResults = modifyAndroidManifest(config.modResults); + return config; + }); +}; diff --git a/apps/mobile/withRiveAssets.js b/apps/mobile/scripts/withRiveAssets.js similarity index 100% rename from apps/mobile/withRiveAssets.js rename to apps/mobile/scripts/withRiveAssets.js diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 5462654d4..f61ae0faf 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -68,11 +68,7 @@ function AppNavigation() { useEffect(() => { const interval = setInterval(() => { - plausibleEvent({ - event: { - type: 'ping' - } - }); + plausibleEvent({ event: { type: 'ping' } }); }, 270 * 1000); return () => clearInterval(interval); diff --git a/apps/mobile/src/components/explorer/Explorer.tsx b/apps/mobile/src/components/explorer/Explorer.tsx index 09ebfecce..2c0adad11 100644 --- a/apps/mobile/src/components/explorer/Explorer.tsx +++ b/apps/mobile/src/components/explorer/Explorer.tsx @@ -13,6 +13,7 @@ import { useActionsModalStore } from '~/stores/modalStore'; import FileItem from './FileItem'; import FileRow from './FileRow'; +import ScreenContainer from '../layout/ScreenContainer'; type ExplorerProps = { items?: ExplorerItem[]; @@ -44,7 +45,7 @@ const Explorer = ({ items }: ExplorerProps) => { } return ( - + {/* Header */} {/* Sort By */} @@ -99,6 +100,7 @@ const Explorer = ({ items }: ExplorerProps) => { )} )} + contentContainerStyle={tw`p-2`} extraData={layoutMode} estimatedItemSize={ layoutMode === 'grid' @@ -107,7 +109,7 @@ const Explorer = ({ items }: ExplorerProps) => { } /> )} - + ); }; diff --git a/apps/mobile/src/components/header/Header.tsx b/apps/mobile/src/components/header/Header.tsx index 0984f40cb..964b561ba 100644 --- a/apps/mobile/src/components/header/Header.tsx +++ b/apps/mobile/src/components/header/Header.tsx @@ -2,7 +2,8 @@ import { useNavigation } from '@react-navigation/native'; import { StackHeaderProps } from '@react-navigation/stack'; import { ArrowLeft, DotsThreeOutline, MagnifyingGlass } from 'phosphor-react-native'; import { lazy } from 'react'; -import { Platform, Pressable, Text, View } from 'react-native'; +import { Pressable, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { tw, twStyle } from '~/lib/tailwind'; import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore'; @@ -44,16 +45,18 @@ export default function Header({ const explorerStore = useExplorerStore(); const routeParams = route?.route.params as any; + const headerHeight = useSafeAreaInsets().top; + return ( - + - + {navBack && ( { @@ -67,7 +70,7 @@ export default function Header({ {title || (routeTitle && route?.options.title)} @@ -128,11 +131,11 @@ interface HeaderIconKindProps { const HeaderIconKind = ({ headerKind, routeParams }: HeaderIconKindProps) => { switch (headerKind) { case 'location': - return ; + return ; case 'tag': return ( diff --git a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx index 9577193b3..04bead07c 100644 --- a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx +++ b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx @@ -12,7 +12,14 @@ import { } from 'phosphor-react-native'; import { PropsWithChildren, useRef } from 'react'; import { Pressable, Text, View, ViewStyle } from 'react-native'; -import { byteSize, getItemFilePath, getItemObject } from '@sd/client'; +import FileViewer from 'react-native-file-viewer'; +import { + byteSize, + getIndexedItemFilePath, + getItemObject, + useLibraryMutation, + useLibraryQuery +} from '@sd/client'; import FileThumb from '~/components/explorer/FileThumb'; import FavoriteButton from '~/components/explorer/sections/FavoriteButton'; import InfoTagPills from '~/components/explorer/sections/InfoTagPills'; @@ -32,7 +39,7 @@ const ActionsContainer = ({ children, style }: ActionsContainerProps) => ( type ActionsItemProps = { title: string; - icon: Icon; + icon?: Icon; onPress?: () => void; isDanger?: boolean; }; @@ -49,7 +56,7 @@ const ActionsItem = ({ icon, onPress, title, isDanger = false }: ActionsItemProp > {title} - + {Icon && } ); }; @@ -62,7 +69,31 @@ export const ActionsModal = () => { const { modalRef, data } = useActionsModalStore(); const objectData = data && getItemObject(data); - const filePath = data && getItemFilePath(data); + const filePath = data && getIndexedItemFilePath(data); + + // Open + + const updateAccessTime = useLibraryMutation('files.updateAccessTime'); + const queriedFullPath = useLibraryQuery(['files.getPath', filePath?.id ?? -1], { + enabled: filePath != null + }); + + async function handleOpen() { + const absolutePath = queriedFullPath.data; + if (!absolutePath) return; + try { + await FileViewer.open(absolutePath, { + // Android only + showAppsSuggestions: false, // If there is not an installed app that can open the file, open the Play Store with suggested apps + showOpenWithDialog: true // if there is more than one app that can open the file, show an Open With dialogue box + }); + filePath && + filePath.object_id && + updateAccessTime.mutateAsync([filePath.object_id]).catch(console.error); + } catch (error) { + // TODO: Handle Error & toast message + } + } return ( <> @@ -99,6 +130,8 @@ export const ActionsModal = () => { {/* Actions */} + + { return ( - - - - ( - - )} - /> - - - + + + ( + + )} + /> + + ); }; diff --git a/apps/mobile/src/components/overview/Locations.tsx b/apps/mobile/src/components/overview/Locations.tsx index 2d96a1b49..73e4b90e1 100644 --- a/apps/mobile/src/components/overview/Locations.tsx +++ b/apps/mobile/src/components/overview/Locations.tsx @@ -23,7 +23,7 @@ const Locations = ({ locations }: Props) => { const modalRef = useRef(null); return ( - + <> @@ -80,7 +80,7 @@ const Locations = ({ locations }: Props) => { - + ); }; diff --git a/apps/mobile/src/main.tsx b/apps/mobile/src/main.tsx index bf8534c90..236bdbdfb 100644 --- a/apps/mobile/src/main.tsx +++ b/apps/mobile/src/main.tsx @@ -94,20 +94,6 @@ window.screen = { height }; -/* - https://github.com/facebook/hermes/issues/23 - - We are using "Hermes" on Android & IOS, which for the current version (0.11), - IOS does not support the Intl fully so we need polyfill it. - - NOTE: We can be picky about what we "polyfill" to optimize but for now this works. -*/ - -if (Platform.OS === 'ios') { - require('intl'); // import intl object - require('intl/locale-data/jsonp/en'); -} - // This is insane. We load all data from `AsyncStorage` into the `_localStorage` global and then once complete we import the app. // This way the polyfilled `localStorage` implementation has its data populated before the global stores within `@sd/client` are initialized (as they are initialized on import). const App = lazy(async () => { diff --git a/apps/mobile/src/screens/Location.tsx b/apps/mobile/src/screens/Location.tsx index 9dd5d283c..e5687ac2d 100644 --- a/apps/mobile/src/screens/Location.tsx +++ b/apps/mobile/src/screens/Location.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { useCache, useLibraryQuery, useNodes } from '@sd/client'; import Explorer from '~/components/explorer/Explorer'; import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack'; @@ -11,21 +11,23 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP useNodes(location.data?.nodes); const locationData = useCache(location.data?.item); - const { data } = useLibraryQuery([ + const paths = useLibraryQuery([ 'search.paths', { filters: [ { filePath: { - locations: { in: [id] }, - path: { path: path ?? '', location_id: id, include_descendants: false } + locations: { in: [id] } + // path: {location_id: id, path: path ?? '', include_descendants: true} // FIXME: This is the correct query, but it doesn't work and then provides a deserialization error. } } ], take: 100 } ]); - const pathsItemsReferences = useMemo(() => data?.items ?? [], [data]); + + const pathsItemsReferences = useMemo(() => paths.data?.items ?? [], [paths.data]); + useNodes(paths.data?.nodes); const pathsItems = useCache(pathsItemsReferences); useEffect(() => { diff --git a/apps/mobile/src/stores/explorerStore.ts b/apps/mobile/src/stores/explorerStore.ts index 6208d0b1b..365930946 100644 --- a/apps/mobile/src/stores/explorerStore.ts +++ b/apps/mobile/src/stores/explorerStore.ts @@ -2,10 +2,13 @@ import { proxy, useSnapshot } from 'valtio'; import { proxySet } from 'valtio/utils'; import { resetStore } from '@sd/client'; -// TODO: Add "media" export type ExplorerLayoutMode = 'list' | 'grid' | 'media'; -export type ExplorerKind = 'Location' | 'Tag' | 'Space'; +export enum ExplorerKind { + Location, + Tag, + Space +} const state = { locationId: null as number | null, diff --git a/interface/app/$libraryId/Layout/Sidebar/JobManager/JobGroup.tsx b/interface/app/$libraryId/Layout/Sidebar/JobManager/JobGroup.tsx index dc8023411..28459fff8 100644 --- a/interface/app/$libraryId/Layout/Sidebar/JobManager/JobGroup.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/JobManager/JobGroup.tsx @@ -251,9 +251,7 @@ function Options({