diff --git a/android/app/build.gradle b/android/app/build.gradle index c55ab66c..d17932ea 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -79,12 +79,12 @@ import com.android.build.OutputFile project.ext.react = [ enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes", - cliPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/cli.js", - hermesCommand: new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/%OS-BIN%/hermesc", - composeSourceMapsPath: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/scripts/compose-source-maps.js", + cliPath: "../../node_modules/react-native/cli.js", + hermesCommand: "../../node_modules/hermes-engine/%OS-BIN%/hermesc", + composeSourceMapsPath: "../../node_modules/react-native/scripts/compose-source-maps.js", ] -apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle") +apply from: "../../node_modules/react-native/react.gradle" /** * Set this to true to create two separate APKs instead of one: @@ -135,8 +135,8 @@ android { applicationId "io.filen.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 218 - versionName "2.0.18" + versionCode 220 + versionName "2.0.20" } splits { abi { @@ -237,8 +237,8 @@ dependencies { } if (enableHermes) { - debugImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-debug.aar")) - releaseImplementation files(new File(["node", "--print", "require.resolve('hermes-engine/package.json')"].execute(null, rootDir).text.trim(), "../android/hermes-release.aar")) + debugImplementation files("../../node_modules/hermes-engine/android/hermes-debug.aar") + releaseImplementation files("../../node_modules/hermes-engine/android/hermes-release.aar") } else { implementation jscFlavor } @@ -253,7 +253,7 @@ task copyDownloadableDepsToLibs(type: Copy) { into 'libs' } -apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); +apply from: "../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle" applyNativeModulesAppBuildGradle(project) //apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3d6c86e6..34740615 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,8 @@ + + com.apple.developer.icloud-container-identifiers - + + iCloud.io.filen.app + com.apple.developer.icloud-services CloudDocuments com.apple.developer.ubiquity-container-identifiers - + + iCloud.io.filen.app + com.apple.security.application-groups group.io.filen.app diff --git a/ios/Filen/Info.plist b/ios/Filen/Info.plist index b6d77238..8813769c 100644 --- a/ios/Filen/Info.plist +++ b/ios/Filen/Info.plist @@ -110,5 +110,11 @@ UIViewControllerBasedStatusBarAppearance + BGTaskSchedulerPermittedIdentifiers + + $(PRODUCT_BUNDLE_IDENTIFIER) + background-fetch + expo-background-fetch + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d24537f8..a1fbc2b5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -469,6 +469,9 @@ PODS: - RNScreens (3.10.2): - React-Core - React-RCTImage + - RNSentry (4.7.1): + - React-Core + - Sentry (= 7.28.0) - RNShareMenu (6.0.0): - React - RNSVG (12.1.1): @@ -479,6 +482,9 @@ PODS: - SDWebImageWebPCoder (0.8.4): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) + - Sentry (7.28.0): + - Sentry/Core (= 7.28.0) + - Sentry/Core (7.28.0) - UMAppLoader (3.0.1) - UMTaskManagerInterface (7.1.1): - ExpoModulesCore @@ -583,6 +589,7 @@ DEPENDENCIES: - RNPermissions (from `../node_modules/react-native-permissions`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) + - "RNSentry (from `../node_modules/@sentry/react-native`)" - RNShareMenu (from `../node_modules/react-native-share-menu`) - RNSVG (from `../node_modules/react-native-svg`) - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`) @@ -609,6 +616,7 @@ SPEC REPOS: - OpenSSL-Universal - SDWebImage - SDWebImageWebPCoder + - Sentry - YogaKit EXTERNAL SOURCES: @@ -756,6 +764,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-reanimated" RNScreens: :path: "../node_modules/react-native-screens" + RNSentry: + :path: "../node_modules/@sentry/react-native" RNShareMenu: :path: "../node_modules/react-native-share-menu" RNSVG: @@ -856,10 +866,12 @@ SPEC CHECKSUMS: RNPermissions: bcd846e8f5a7f39e921cc7ca7172e2de0e698b6f RNReanimated: 46cdb89ca59ab7181334f4ed05a70e82ddb36751 RNScreens: d6da2b9e29cf523832c2542f47bf1287318b1868 + RNSentry: 694aecc3d8240e4935374974a6636e360ae06394 RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3 RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 + Sentry: 2c6053e4cfe6dea6608135dea1928ffbb4ecfba5 UMAppLoader: 2ee9f1278d315672f4854c6a2310aa9988f61f72 UMTaskManagerInterface: 3184c93ecc290bd422c6e344badc89b601e9c29b Yoga: 5cbf25add73edb290e1067017690f7ebf56c5468 diff --git a/ios/sentry.properties b/ios/sentry.properties new file mode 100644 index 00000000..ee9840e8 --- /dev/null +++ b/ios/sentry.properties @@ -0,0 +1,4 @@ +defaults.url=https://sentry.io/ +defaults.org=filen-cloud-dienste-ug +defaults.project=react-native +auth.token=237c8075f3c441fd8d1f48d0e0dd402c3a5960b9f0f0444eb0732171529a00ac diff --git a/package.json b/package.json index d0f90971..66f07fe1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "filen-mobile", - "version": "2.0.18", + "version": "2.0.20", "private": true, "scripts": { "android": "react-native run-android", @@ -8,7 +8,7 @@ "start": "react-native start", "test": "jest", "lint": "eslint .", - "dist-android": "./android/gradlew bundleRelease && ./android/gradlew assembleRelease", + "dist-android": "cd ./android && ./gradlew bundleRelease && ./gradlew assembleRelease && cd ..", "splash": "react-native generate-bootsplash bootsplash.png --background-color=#263179 --logo-width=100 --flavor=main --assets-path=src/assets", "icons": "react-native-svg-app-icon", "install-release-apk": "adb install android/app/build/outputs/apk/release/app-release.apk" @@ -23,6 +23,7 @@ "@react-navigation/bottom-tabs": "^6.2.0", "@react-navigation/native": "^6.0.8", "@react-navigation/native-stack": "^6.4.0", + "@sentry/react-native": "^4.7.1", "axios": "^0.27.1", "crypto-js": "^4.1.1", "expo": "^44.0.6", diff --git a/src/App.tsx b/src/App.tsx index 49fda414..7d2fcdb7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import "./lib/globals" import "./lib/node" import React, { useState, useEffect, Fragment, memo } from "react" -import { Dimensions, View, Platform, DeviceEventEmitter, LogBox, Appearance, AppState, Alert } from "react-native" +import { Dimensions, View, Platform, DeviceEventEmitter, LogBox, Appearance, AppState } from "react-native" import { setup } from "./lib/services/setup/setup" import storage from "./lib/storage" import { useMMKVBoolean, useMMKVString, useMMKVNumber } from "react-native-mmkv" @@ -44,35 +44,15 @@ import { TextEditorScreen } from "./screens/TextEditorScreen/TextEditorScreen" import { checkAppVersion } from "./lib/services/versionCheck" import { UpdateScreen } from "./screens/UpdateScreen/UpdateScreen" import BackgroundTimer from "react-native-background-timer" -import { setJSExceptionHandler, setNativeExceptionHandler } from "react-native-exception-handler" -import { reportError } from "./lib/api" import ImageViewerScreen from "./screens/ImageViewerScreen/ImageViewerScreen" import { CameraUploadAlbumsScreen } from "./screens/CameraUploadAlbumsScreen/CameraUploadAlbumsScreen" import { isRouteInStack } from "./lib/helpers" +import * as Sentry from "@sentry/react-native" -setJSExceptionHandler((err) => { - reportError(err.toString()) - - Alert.alert("Unexpected error occured", - ` - Error: ${err.name} ${err.message} - - The error has been automatically reported to us. Please restart the app if it does not continue to work! - `, - [ - { - text: "Close", - onPress: () => { - return false - } - } - ] - ) -}, true) - -setNativeExceptionHandler((err) => { - reportError(err) -}, false) +Sentry.init({ + dsn: "https://1aa0cbb262634a27a5887e91381e4251@o4504039703314432.ingest.sentry.io/4504039705804800", + tracesSampleRate: 1.0 +}) NetInfo.configure({ reachabilityUrl: "https://api.filen.io", @@ -103,7 +83,7 @@ DeviceEventEmitter.addListener("event", (data) => { storage.set("cameraUploadUploaded", 0) storage.set("cameraUploadTotal", 0) -export const App = memo(() => { +export const App = Sentry.wrap(memo(() => { const [isLoggedIn, setIsLoggedIn] = useMMKVBoolean("isLoggedIn", storage) const setDimensions = useStore(state => state.setDimensions) const [darkMode, setDarkMode] = useMMKVBoolean("darkMode", storage) @@ -641,4 +621,4 @@ export const App = memo(() => { /> ) -}) \ No newline at end of file +})) \ No newline at end of file diff --git a/src/components/ActionSheets/ActionSheets.tsx b/src/components/ActionSheets/ActionSheets.tsx index b2cff6a5..69b07c12 100644 --- a/src/components/ActionSheets/ActionSheets.tsx +++ b/src/components/ActionSheets/ActionSheets.tsx @@ -1489,7 +1489,7 @@ export const ItemActionSheetItemHeader = memo(() => { } {hideSizes ? formatBytes(0) : formatBytes(currentActionSheetItem.type == "file" ? currentActionSheetItem.size : storage.getNumber("folderSizeCache:" + currentActionSheetItem.uuid))} { - typeof currentActionSheetItem.sharerEmail == "string" && currentActionSheetItem.sharerEmail.length > 0 && ( + typeof currentActionSheetItem.sharerEmail == "string" && currentActionSheetItem.sharerEmail.length > 0 && getParent().length < 32 && ( <>   •   {currentActionSheetItem.sharerEmail} @@ -1497,10 +1497,15 @@ export const ItemActionSheetItemHeader = memo(() => { ) } { - typeof currentActionSheetItem.receiverEmail == "string" && currentActionSheetItem.receiverEmail.length > 0 && ( + typeof currentActionSheetItem.receivers !== "undefined" && Array.isArray(currentActionSheetItem.receivers) && currentActionSheetItem.receivers.length > 0 && getParent().length < 32 && ( <>   •   - {currentActionSheetItem.receiverEmail} + +  {currentActionSheetItem.receivers.length} ) } @@ -2079,7 +2084,7 @@ export const ItemActionSheet = memo(({ navigation }: ItemActionSheetProps) => { ) } { - isDeviceOnline && routeURL.indexOf("shared-in") !== -1 && ( + isDeviceOnline && routeURL.indexOf("shared-in") !== -1 && getParent().length < 32 && ( { await SheetManager.hide("ItemActionSheet") @@ -2092,7 +2097,7 @@ export const ItemActionSheet = memo(({ navigation }: ItemActionSheetProps) => { ) } { - isDeviceOnline && routeURL.indexOf("shared-out") !== -1 && ( + isDeviceOnline && routeURL.indexOf("shared-out") !== -1 && getParent().length < 32 && ( { await SheetManager.hide("ItemActionSheet") diff --git a/src/components/Dialogs/Dialogs.tsx b/src/components/Dialogs/Dialogs.tsx index f77add09..4c75c9c3 100644 --- a/src/components/Dialogs/Dialogs.tsx +++ b/src/components/Dialogs/Dialogs.tsx @@ -11,6 +11,7 @@ import { DeviceEventEmitter, Keyboard } from "react-native" import { logout } from "../../lib/services/auth/logout" import { navigationAnimation } from "../../lib/state" import { StackActions, CommonActions } from "@react-navigation/native" +import type { Item } from "../../lib/services/items" export const RenameDialog = memo(() => { const [darkMode, setDarkMode] = useMMKVBoolean("darkMode", storage) @@ -495,12 +496,30 @@ export const ConfirmStopSharingDialog = memo(() => { label={i18n(lang, "stopSharing")} disabled={buttonsDisabled} onPress={() => { + if(typeof currentActionSheetItem.receivers == "undefined" || !Array.isArray(currentActionSheetItem.receivers)){ + return + } + setButtonsDisabled(true) setStopSharingDialogVisible(false) useStore.setState({ fullscreenLoadingModalVisible: true }) - stopSharingItem({ item: currentActionSheetItem }).then(async () => { + const promises = [] + + for(let i = 0; i < currentActionSheetItem.receivers.length; i++){ + const item: Item = { + ...currentActionSheetItem, + receiverId: currentActionSheetItem.receivers[i].id, + receiverEmail: currentActionSheetItem.receivers[i].email + } + + promises.push(new Promise((resolve, reject) => { + stopSharingItem({ item }).then(resolve).catch(reject) + })) + } + + Promise.all(promises).then(() => { DeviceEventEmitter.emit("event", { type: "remove-item", data: { diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx index be94bb36..769638cd 100644 --- a/src/components/Item/Item.tsx +++ b/src/components/Item/Item.tsx @@ -3,7 +3,7 @@ import { Text, View, TouchableOpacity, TouchableHighlight, DeviceEventEmitter, D import FastImage from "react-native-fast-image" import Ionicon from "@expo/vector-icons/Ionicons" import { getImageForItem } from "../../assets/thumbnails" -import { formatBytes, getFolderColor, calcPhotosGridSize, getRouteURL } from "../../lib/helpers" +import { formatBytes, getFolderColor, calcPhotosGridSize, getRouteURL, getParent } from "../../lib/helpers" import { i18n } from "../../i18n" import { getColor } from "../../lib/style/colors" import RNFS from "react-native-fs" @@ -52,7 +52,10 @@ export const ListItem = memo(({ item, index, darkMode, hideFileNames, hideSizes, fetchFolderSize({ folder: item, routeURL: getRouteURL() }).then((fetchedSize) => { storage.set("folderSizeCache:" + item.uuid, fetchedSize) - storage.set("fetchFolderSizeTimeout:" + item.uuid, (new Date().getTime() + 60000)) + + if(fetchedSize > 0){ + storage.set("fetchFolderSizeTimeout:" + item.uuid, (new Date().getTime() + 60000)) + } if(isMounted()){ setFolderSize(fetchedSize) @@ -172,7 +175,8 @@ export const ListItem = memo(({ item, index, darkMode, hideFileNames, hideSizes, typeof item.offline == "boolean" && item.offline && ( <>   •   @@ -193,7 +197,7 @@ export const ListItem = memo(({ item, index, darkMode, hideFileNames, hideSizes, } {hideSizes ? formatBytes(0) : formatBytes(item.type == "file" ? item.size : folderSize)} { - typeof item.sharerEmail == "string" && item.sharerEmail.length > 0 && ( + typeof item.sharerEmail == "string" && item.sharerEmail.length > 0 && getParent().length < 32 && ( <>   •   {item.sharerEmail} @@ -201,10 +205,15 @@ export const ListItem = memo(({ item, index, darkMode, hideFileNames, hideSizes, ) } { - typeof item.receiverEmail == "string" && item.receiverEmail.length > 0 && ( + typeof item.receivers !== "undefined" && Array.isArray(item.receivers) && item.receivers.length > 0 && getParent().length < 32 && ( <>   •   - {item.receiverEmail} + +  {item.receivers.length} ) } diff --git a/src/lib/services/items/items.ts b/src/lib/services/items/items.ts index 6792f64a..1605e3f3 100644 --- a/src/lib/services/items/items.ts +++ b/src/lib/services/items/items.ts @@ -16,7 +16,12 @@ import { navigationAnimation } from "../../state" import memoryCache from "../../memoryCache" const isGeneratingThumbnailForItemUUID: any = {} -const isCheckingThumbnailForItemUUID : any= {} +const isCheckingThumbnailForItemUUID : any = {} + +export interface ItemReceiver { + id: number, + email: string +} export interface Item { id: string, @@ -48,7 +53,8 @@ export interface Item { chunks: number, thumbnail: string | undefined, version: number, - hash: string + hash: string, + receivers?: ItemReceiver[] } export const ItemTemplate: Item = { @@ -601,6 +607,39 @@ export const loadItems = async ({ parent, prevItems, setItems, masterKeys, setLo storage.set("itemCache:file:" + file.uuid, JSON.stringify(item)) } + + const groups: Item[] = [] + const sharedTo: { [key: string]: ItemReceiver[] } = {} + const added: { [key: string]: boolean } = {} + + for(let i = 0; i < items.length; i++){ + if(Array.isArray(sharedTo[items[i].uuid])){ + sharedTo[items[i].uuid].push({ + id: items[i].receiverId, + email: items[i].receiverEmail + }) + } + else{ + sharedTo[items[i].uuid] = [{ + id: items[i].receiverId, + email: items[i].receiverEmail + }] + } + } + + for(let i = 0; i < items.length; i++){ + if(Array.isArray(sharedTo[items[i].uuid])){ + items[i].receivers = sharedTo[items[i].uuid] + } + + if(!added[items[i].uuid]){ + added[items[i].uuid] = true + + groups.push(items[i]) + } + } + + items = groups } else if(parent == "photos"){ try{ diff --git a/src/lib/services/setup/setup.ts b/src/lib/services/setup/setup.ts index 7b7c1888..2c3025db 100644 --- a/src/lib/services/setup/setup.ts +++ b/src/lib/services/setup/setup.ts @@ -12,6 +12,10 @@ export const clearCacheDirectories = (): Promise => { getDownloadPath({ type: "cachedDownloads" }).then((cachedDownloadsPath) => { RNFS.readDir(RNFS.TemporaryDirectoryPath).then(async (items) => { for(let i = 0; i < items.length; i++){ + if(items[i].path.indexOf("SentryCrash") !== -1){ + continue + } + try{ await RNFS.unlink(items[i].path) } @@ -22,6 +26,10 @@ export const clearCacheDirectories = (): Promise => { RNFS.readDir(RNFS.CachesDirectoryPath).then(async (items) => { for(let i = 0; i < items.length; i++){ + if(items[i].path.indexOf("SentryCrash") !== -1){ + continue + } + try{ await RNFS.unlink(items[i].path) } @@ -32,6 +40,10 @@ export const clearCacheDirectories = (): Promise => { RNFS.readDir(cachedDownloadsPath).then(async (items) => { for(let i = 0; i < items.length; i++){ + if(items[i].path.indexOf("SentryCrash") !== -1){ + continue + } + try{ await RNFS.unlink(items[i].path) } diff --git a/src/screens/SettingsScreen/SettingsScreen.tsx b/src/screens/SettingsScreen/SettingsScreen.tsx index 655d21e2..2f7c356b 100644 --- a/src/screens/SettingsScreen/SettingsScreen.tsx +++ b/src/screens/SettingsScreen/SettingsScreen.tsx @@ -18,6 +18,7 @@ import { hasStoragePermissions } from "../../lib/permissions" import { SheetManager } from "react-native-actions-sheet" import { setStatusBarStyle } from "../../lib/statusbar" import * as MediaLibrary from "expo-media-library" +import * as Sentry from "@sentry/react-native" const MISC_BASE_PATH: string = RNFS.DocumentDirectoryPath + (RNFS.DocumentDirectoryPath.slice(-1) == "/" ? "" : "/") + "misc/"