diff --git a/android/app/build.gradle b/android/app/build.gradle
index 40a3494ac..c20f0e5dd 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -93,8 +93,8 @@ android {
applicationId 'com.internxt.cloud'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 125
- versionName "1.10.0"
+ versionCode 129
+ versionName "1.10.2"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 8f9a60531..f0d4ea285 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
Internxt
automatic
- 1.10.0
+ 1.10.2
contain
false
\ No newline at end of file
diff --git a/assets/lang/strings.ts b/assets/lang/strings.ts
index 9667d4559..cf28bca07 100644
--- a/assets/lang/strings.ts
+++ b/assets/lang/strings.ts
@@ -858,6 +858,7 @@ const translations = {
SignOutModal: {
title: 'Log out from this account?',
signingOut: 'Saving your latest Photos data…',
+ signingOutGeneric: 'Signing out…',
},
ConfirmMoveItemModal: {
title: 'Move {0} item(s) from',
@@ -1868,6 +1869,7 @@ const translations = {
SignOutModal: {
title: '¿Salir de esta cuenta?',
signingOut: 'Guardando tus últimos datos de Photos…',
+ signingOutGeneric: 'Cerrando sesión…',
},
ConfirmMoveItemModal: {
title: 'Mover {0} item(s) desde',
diff --git a/ios/Internxt/Info.plist b/ios/Internxt/Info.plist
index 02a5e6b8b..941e5c8da 100644
--- a/ios/Internxt/Info.plist
+++ b/ios/Internxt/Info.plist
@@ -23,7 +23,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.10.0
+ 1.10.2
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/Internxt/Supporting/Expo.plist b/ios/Internxt/Supporting/Expo.plist
index f8db468be..778050ce9 100644
--- a/ios/Internxt/Supporting/Expo.plist
+++ b/ios/Internxt/Supporting/Expo.plist
@@ -9,7 +9,7 @@
EXUpdatesLaunchWaitMs
0
EXUpdatesRuntimeVersion
- 1.10.0
+ 1.10.2
EXUpdatesURL
https://u.expo.dev/680f4feb-6315-4a50-93ec-36dcd0b831d2
diff --git a/package.json b/package.json
index f1efa8300..41aa2c033 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "drive-mobile",
- "version": "v1.10.0",
+ "version": "v1.10.2",
"private": true,
"license": "GNU",
"scripts": {
diff --git a/src/App.tsx b/src/App.tsx
index 86f08598b..c618ebd06 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -249,13 +249,7 @@ function AppContent(): JSX.Element {
onClose={onChangeProfilePictureModalClosed}
/>
- {
- if (navigationRef.isReady()) {
- navigationRef.reset({ index: 0, routes: [{ name: 'SignIn' }] });
- }
- }}
- />
+
) : (
diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx
index 9b333b6f6..9d03b036e 100644
--- a/src/components/SearchInput/SearchInput.tsx
+++ b/src/components/SearchInput/SearchInput.tsx
@@ -1,4 +1,4 @@
-import { MagnifyingGlass, XCircle } from 'phosphor-react-native';
+import { MagnifyingGlassIcon, XCircleIcon } from 'phosphor-react-native';
import { createRef, useState } from 'react';
import { StyleProp, TextInput, TouchableOpacity, View, ViewStyle } from 'react-native';
import { useTailwind } from 'tailwind-rn';
@@ -50,7 +50,7 @@ export function SearchInput(props: SearchInputProps): JSX.Element {
{!isFocused && (
-
+
)}
@@ -62,8 +62,14 @@ export function SearchInput(props: SearchInputProps): JSX.Element {
value={props.value}
style={[
styles.fontWeight.regular,
- tailwind('text-base pl-3 h-9 flex-1'),
- { marginBottom: 2, color: getColor('text-gray-100') },
+ tailwind('pl-3 h-9 flex-1'),
+ {
+ fontSize: 16,
+ color: getColor('text-gray-100'),
+ paddingVertical: 0,
+ textAlignVertical: 'center',
+ includeFontPadding: false,
+ },
]}
placeholder={props.placeholder || ''}
placeholderTextColor={getColor('text-gray-80')}
@@ -72,7 +78,7 @@ export function SearchInput(props: SearchInputProps): JSX.Element {
{!!props.value && (
-
+
)}
diff --git a/src/components/modals/SignOutModal/index.tsx b/src/components/modals/SignOutModal/index.tsx
index fdc009e5f..2c8d249ac 100644
--- a/src/components/modals/SignOutModal/index.tsx
+++ b/src/components/modals/SignOutModal/index.tsx
@@ -3,6 +3,7 @@ import { View } from 'react-native';
import { useTailwind } from 'tailwind-rn';
import strings from '../../../../assets/lang/strings';
+import appService from '../../../services/AppService';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import { authSelectors, authThunks } from '../../../store/slices/auth';
import { uiActions } from '../../../store/slices/ui';
@@ -12,11 +13,7 @@ import LoadingSpinner from '../../LoadingSpinner';
import UserProfilePicture from '../../UserProfilePicture';
import CenterModal from '../CenterModal';
-interface SignOutModalProps {
- readonly onSignedOut: () => void;
-}
-
-function SignOutModal({ onSignedOut }: SignOutModalProps): JSX.Element {
+function SignOutModal(): JSX.Element {
const tailwind = useTailwind();
const dispatch = useAppDispatch();
const userFullName = useAppSelector(authSelectors.userFullName);
@@ -40,7 +37,6 @@ function SignOutModal({ onSignedOut }: SignOutModalProps): JSX.Element {
await dispatch(authThunks.signOutThunk({ reason: 'manual' }));
isSigningOutRef.current = false;
setIsSigningOut(false);
- onSignedOut();
onClosed();
};
@@ -57,7 +53,9 @@ function SignOutModal({ onSignedOut }: SignOutModalProps): JSX.Element {
- {strings.modals.SignOutModal.signingOut}
+ {appService.isPhotosEnabled
+ ? strings.modals.SignOutModal.signingOut
+ : strings.modals.SignOutModal.signingOutGeneric}
) : (
diff --git a/src/navigation/AppStackNavigator.tsx b/src/navigation/AppStackNavigator.tsx
new file mode 100644
index 000000000..8ee60a3e1
--- /dev/null
+++ b/src/navigation/AppStackNavigator.tsx
@@ -0,0 +1,51 @@
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { Platform } from 'react-native';
+
+import { LargeShareUploadScreen } from '../screens/LargeShareUpload/LargeShareUploadScreen';
+import { DeactivatedAccountScreen } from '../screens/DeactivatedAccountScreen';
+import DebugScreen from '../screens/DebugScreen';
+import { TrashScreen } from '../screens/common/TrashScreen';
+import { DrivePreviewScreen } from '../screens/drive/DrivePreviewScreen';
+import { PhotoPreviewScreen } from '../screens/PhotoPreviewScreen';
+import { SettingsNavigator } from './SettingsNavigator';
+import AuthenticatedNavigator from './TabExplorerNavigator';
+import ShareExtensionView from '../shareExtension/ShareExtensionView.android';
+import { RootStackParamList } from '../types/navigation';
+
+const Stack = createNativeStackNavigator();
+
+export default function AppStackNavigator(): JSX.Element {
+ return (
+
+
+
+
+
+
+
+
+ {Platform.OS === 'android' && (
+
+ )}
+ {Platform.OS === 'ios' && (
+
+ )}
+
+ );
+}
diff --git a/src/navigation/AuthStackNavigator.tsx b/src/navigation/AuthStackNavigator.tsx
new file mode 100644
index 000000000..8e5a9f88c
--- /dev/null
+++ b/src/navigation/AuthStackNavigator.tsx
@@ -0,0 +1,27 @@
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { Platform } from 'react-native';
+
+import SignInScreen from '../screens/SignInScreen';
+import WebLoginScreen from '../screens/WebLoginScreen';
+import DebugScreen from '../screens/DebugScreen';
+import ShareExtensionView from '../shareExtension/ShareExtensionView.android';
+import { RootStackParamList } from '../types/navigation';
+
+const Stack = createNativeStackNavigator();
+
+export default function AuthStackNavigator(): JSX.Element {
+ return (
+
+
+
+
+ {Platform.OS === 'android' && (
+
+ )}
+
+ );
+}
diff --git a/src/navigation/RootNavigator.tsx b/src/navigation/RootNavigator.tsx
index d9ef3b4b4..a9f12244b 100644
--- a/src/navigation/RootNavigator.tsx
+++ b/src/navigation/RootNavigator.tsx
@@ -1,26 +1,14 @@
import { NavigationContainerRef } from '@react-navigation/native';
-import { createNativeStackNavigator } from '@react-navigation/native-stack';
-import { Platform, View } from 'react-native';
-import { LargeShareUploadScreen } from '../screens/LargeShareUpload/LargeShareUploadScreen';
+import { View } from 'react-native';
-import SignInScreen from '../screens/SignInScreen';
-import WebLoginScreen from '../screens/WebLoginScreen';
import { useAppSelector } from '../store/hooks';
import { RootStackParamList } from '../types/navigation';
-import AuthenticatedNavigator from './TabExplorerNavigator';
import { useDeepLinks } from '../hooks/useDeepLinks';
-import { DeactivatedAccountScreen } from '../screens/DeactivatedAccountScreen';
-import DebugScreen from '../screens/DebugScreen';
-import { TrashScreen } from '../screens/common/TrashScreen';
-import { DrivePreviewScreen } from '../screens/drive/DrivePreviewScreen';
-import { PhotoPreviewScreen } from '../screens/PhotoPreviewScreen';
-import { SettingsNavigator } from './SettingsNavigator';
-import ShareExtensionView from '../shareExtension/ShareExtensionView.android';
import { useIosPendingShareHandoff } from '../shareExtension/hooks/useIosPendingShareHandoff';
import { useAndroidShareIntent } from '../shareExtension/useAndroidShareIntent';
-
-const Stack = createNativeStackNavigator();
+import AppStackNavigator from './AppStackNavigator';
+import AuthStackNavigator from './AuthStackNavigator';
type Props = {
navigationContainerRef?: NavigationContainerRef;
@@ -35,43 +23,7 @@ function AppNavigator({ navigationContainerRef }: Readonly): JSX.Element
if (isLoggedIn == null) return ;
- const initialRouteName: keyof RootStackParamList = isLoggedIn ? 'TabExplorer' : 'SignIn';
-
- return (
-
-
-
-
-
-
-
-
-
-
- {Platform.OS === 'android' && (
-
- )}
- {Platform.OS === 'ios' && (
-
- )}
-
- );
+ return isLoggedIn ? : ;
}
export default AppNavigator;
diff --git a/src/navigation/TabExplorerNavigator.tsx b/src/navigation/TabExplorerNavigator.tsx
index bb4033a86..73de7a67d 100644
--- a/src/navigation/TabExplorerNavigator.tsx
+++ b/src/navigation/TabExplorerNavigator.tsx
@@ -5,7 +5,6 @@ import { BottomTabBarProps } from '@react-navigation/bottom-tabs/lib/typescript/
import { useEffect } from 'react';
import { AppState, AppStateStatus, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import { authThunks } from 'src/store/slices/auth';
import { runBackupCycleThunk } from 'src/store/slices/photos';
import { storageThunks } from 'src/store/slices/storage';
import { useTailwind } from 'tailwind-rn';
@@ -60,7 +59,6 @@ export default function TabExplorerNavigator(props: RootStackScreenProps<'TabExp
} catch {
const isDeletingAccount = await asyncStorageService.getItem(AsyncStorageKey.IsDeletingAccount);
if (isDeletingAccount) {
- dispatch(authThunks.signOutThunk({ reason: 'manual' }));
props.navigation.replace('DeactivatedAccount');
}
}
diff --git a/src/screens/DeactivatedAccountScreen/DeactivatedAccountScreen.tsx b/src/screens/DeactivatedAccountScreen/DeactivatedAccountScreen.tsx
index 760c3e726..1f2014300 100644
--- a/src/screens/DeactivatedAccountScreen/DeactivatedAccountScreen.tsx
+++ b/src/screens/DeactivatedAccountScreen/DeactivatedAccountScreen.tsx
@@ -1,6 +1,5 @@
import React from 'react';
-import { RootStackScreenProps } from '../../types/navigation';
import InternxtLogo from '../../../assets/logo.svg';
import AppScreen from '../../components/AppScreen';
import AppButton from '../../components/AppButton';
@@ -12,12 +11,15 @@ import { Dimensions, Linking, View } from 'react-native';
import AppVersionWidget from 'src/components/AppVersionWidget';
import { LinearGradient } from 'expo-linear-gradient';
import appService from 'src/services/AppService';
+import { useAppDispatch } from 'src/store/hooks';
+import { authThunks } from 'src/store/slices/auth';
-export function DeactivatedAccountScreen({ navigation }: RootStackScreenProps<'DeactivatedAccount'>): JSX.Element {
+export function DeactivatedAccountScreen(): JSX.Element {
const tailwind = useTailwind();
+ const dispatch = useAppDispatch();
const handleGoToSignIn = () => {
- navigation.replace('SignIn');
+ dispatch(authThunks.signOutThunk({ reason: 'manual' }));
};
const handleGoToSignUp = async () => {
const webAuthUrl = appService.urls.webAuth.signup;
diff --git a/src/screens/PhotoPreviewScreen/index.tsx b/src/screens/PhotoPreviewScreen/index.tsx
index db0e7d609..50807d1a3 100644
--- a/src/screens/PhotoPreviewScreen/index.tsx
+++ b/src/screens/PhotoPreviewScreen/index.tsx
@@ -1,6 +1,6 @@
import { useNavigation } from '@react-navigation/native';
import strings from 'assets/lang/strings';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { StatusBar, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
@@ -53,27 +53,32 @@ export const PhotoPreviewScreen = ({ route }: Props): JSX.Element => {
}
}, []);
- const handleSwipeDown = useCallback(() => {
+ const hasClosedRef = useRef(false);
+ const closePreview = useCallback(() => {
+ if (hasClosedRef.current) {
+ return;
+ }
+ hasClosedRef.current = true;
navigation.goBack();
}, [navigation]);
+ const handleSwipeDown = useCallback(() => {
+ closePreview();
+ }, [closePreview]);
+
const exitVideoMode = useCallback(() => {
setHasVideoStarted(false);
resetVideoPlayer();
setIsUiVisible(true);
}, [resetVideoPlayer]);
- const closePreviewScreen = useCallback(() => {
- navigation.goBack();
- }, [navigation]);
-
const handleClose = useCallback(() => {
if (hasVideoStarted) {
exitVideoMode();
} else {
- closePreviewScreen();
+ closePreview();
}
- }, [hasVideoStarted, exitVideoMode, closePreviewScreen]);
+ }, [hasVideoStarted, exitVideoMode, closePreview]);
const handleVideoPlay = useCallback(() => {
setHasVideoStarted(true);
@@ -132,8 +137,8 @@ export const PhotoPreviewScreen = ({ route }: Props): JSX.Element => {
}, [onItemChanged]),
onAfterTrash: useCallback(async () => {
await onItemChanged?.();
- navigation.goBack();
- }, [onItemChanged, navigation]),
+ closePreview();
+ }, [onItemChanged, closePreview]),
onAfterRestore: useCallback(
async (restoredItems: TimelinePhotoItem[]) => {
for (const restoredItem of restoredItems) {
diff --git a/src/screens/PhotosScreen/EnableBackupBottomSheet.tsx b/src/screens/PhotosScreen/EnableBackupBottomSheet.tsx
index 9b390ff13..6f6387ab1 100644
--- a/src/screens/PhotosScreen/EnableBackupBottomSheet.tsx
+++ b/src/screens/PhotosScreen/EnableBackupBottomSheet.tsx
@@ -60,7 +60,7 @@ const EnableBackupBottomSheet = ({ isOpen, onClose, onSuccess }: EnableBackupBot
};
return (
-
+
{
@@ -51,7 +50,12 @@ const ActionRow = ({ icon, label, onPress, isDestructive, isLast }: ActionRowPro
style={[tailwind('flex-row items-center px-4'), styles.row]}
hitSlop={{ top: 2, bottom: 2 }}
>
-
+
{icon}
-
+
{actionStrings.back}
diff --git a/src/screens/SignInScreen/index.tsx b/src/screens/SignInScreen/index.tsx
index cb8058894..a90a9e6c1 100644
--- a/src/screens/SignInScreen/index.tsx
+++ b/src/screens/SignInScreen/index.tsx
@@ -69,7 +69,6 @@ function SignInScreen(): JSX.Element {
return (
{isDark ? (
diff --git a/src/screens/WebLoginScreen/index.tsx b/src/screens/WebLoginScreen/index.tsx
index dc55116a6..589b44867 100644
--- a/src/screens/WebLoginScreen/index.tsx
+++ b/src/screens/WebLoginScreen/index.tsx
@@ -48,16 +48,16 @@ function WebLoginScreen({ route, navigation }: RootStackScreenProps<'WebLogin'>)
privateKey,
});
- await dispatch(
- authThunks.signInThunk({
- user: result.user,
- token: result.token,
- newToken: result.newToken,
- }),
- ).unwrap();
-
setLoadingState('success');
- setTimeout(() => navigation.replace('TabExplorer', { screen: 'Home' }), 800);
+ setTimeout(() => {
+ dispatch(
+ authThunks.signInThunk({
+ user: result.user,
+ token: result.token,
+ newToken: result.newToken,
+ }),
+ );
+ }, 800);
} catch (error) {
const castedError = errorService.castError(error);
setLoadingState('error');
diff --git a/src/screens/drive/DriveFolderScreen/DriveFolderScreenHeader.tsx b/src/screens/drive/DriveFolderScreen/DriveFolderScreenHeader.tsx
index 5ad895317..db48d6248 100644
--- a/src/screens/drive/DriveFolderScreen/DriveFolderScreenHeader.tsx
+++ b/src/screens/drive/DriveFolderScreen/DriveFolderScreenHeader.tsx
@@ -133,7 +133,7 @@ export const DriveFolderScreenHeader: React.FC = (
onFocusChange={handleSearchInputFocusChange}
/>
- = (
{strings.screens.drive.searchInAllFolders}
-
+ */}
diff --git a/src/screens/drive/DriveFolderScreen/search/GlobalSearchModal.tsx b/src/screens/drive/DriveFolderScreen/search/GlobalSearchModal.tsx
index 649bb4b00..487f5e4f9 100644
--- a/src/screens/drive/DriveFolderScreen/search/GlobalSearchModal.tsx
+++ b/src/screens/drive/DriveFolderScreen/search/GlobalSearchModal.tsx
@@ -6,16 +6,8 @@ import { DriveListType } from '@internxt-mobile/types/drive/ui';
import { useNavigation } from '@react-navigation/native';
import { ArrowLeft, MagnifyingGlass } from 'phosphor-react-native';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import {
- ActivityIndicator,
- KeyboardAvoidingView,
- Modal,
- Platform,
- SafeAreaView,
- TextInput,
- TouchableOpacity,
- View,
-} from 'react-native';
+import { ActivityIndicator, KeyboardAvoidingView, Modal, Platform, TextInput, TouchableOpacity, View } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
import AppText from 'src/components/AppText';
import DriveList from 'src/components/drive/lists/DriveList/DriveList';
import useGetColor from 'src/hooks/useColor';
@@ -39,6 +31,7 @@ interface GlobalSearchModalProps {
export const GlobalSearchModal: React.FC = ({ visible, onClose, onItemPress }) => {
const tailwind = useTailwind();
const getColor = useGetColor();
+ const insets = useSafeAreaInsets();
const [inputRef, setInputRef] = useState(null);
const [isNavigating, setIsNavigating] = useState(false);
const navigation = useNavigation['navigation']>();
@@ -273,8 +266,19 @@ export const GlobalSearchModal: React.FC = ({ visible, o
};
return (
-
-
+
+
{/* Header */}
@@ -299,9 +303,19 @@ export const GlobalSearchModal: React.FC = ({ visible, o
onChangeText={updateQuery}
placeholder={strings.modals.GlobalSearchModal.searchPlaceholder}
placeholderTextColor={getColor('text-gray-40')}
- style={[tailwind('flex-1 ml-2 text-base'), { color: getColor('text-gray-100') }]}
+ style={[
+ tailwind('flex-1 ml-2'),
+ {
+ fontSize: 16,
+ color: getColor('text-gray-100'),
+ paddingVertical: 0,
+ textAlignVertical: 'center',
+ includeFontPadding: false,
+ },
+ ]}
autoCapitalize="none"
autoCorrect={false}
+ spellCheck={false}
returnKeyType="search"
editable={!isNavigating}
/>
@@ -363,7 +377,7 @@ export const GlobalSearchModal: React.FC = ({ visible, o
renderEmptyState()
)}
-
+
);
};
diff --git a/src/store/slices/photos/index.spec.ts b/src/store/slices/photos/index.spec.ts
index 6914f8647..ed5adb137 100644
--- a/src/store/slices/photos/index.spec.ts
+++ b/src/store/slices/photos/index.spec.ts
@@ -104,6 +104,12 @@ jest.mock('src/store/slices/storage', () => ({
},
}));
+jest.mock('src/services/AppService', () => ({
+ ...jest.requireActual('src/services/AppService'),
+ __esModule: true,
+ default: { isPhotosEnabled: true },
+}));
+
jest.mock('./selectors', () => ({
hasPhotosFeatureAccess: jest.fn().mockReturnValue(true),
}));
diff --git a/src/store/slices/photos/index.ts b/src/store/slices/photos/index.ts
index 956787d5b..12907814c 100644
--- a/src/store/slices/photos/index.ts
+++ b/src/store/slices/photos/index.ts
@@ -1,6 +1,7 @@
import { AxiosResponseError } from '@internxt/sdk/dist/shared/types/errors';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Platform } from 'react-native';
+import appService from 'src/services/AppService';
import asyncStorageService from 'src/services/AsyncStorageService';
import errorService from 'src/services/ErrorService';
import { networkMonitorService, NetworkStateType } from 'src/services/NetworkMonitorService';
@@ -387,6 +388,10 @@ export const forceRefreshThunk = createAsyncThunk(
'photos/runBackupCycle',
async (_, { getState, dispatch }) => {
+ if (!appService.isPhotosEnabled) {
+ return;
+ }
+
const { syncStatus } = getState().photos;
if (syncStatus === 'scanning' || syncStatus === 'uploading' || syncStatus === 'pausing') {
return;