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({