Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ android {
applicationId 'com.internxt.cloud'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 122
versionCode 123
versionName "1.9.0"

buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
Expand Down
4 changes: 4 additions & 0 deletions assets/lang/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ const translations = {
folderNameLabel: 'Name',
folderNamePlaceholder: 'Folder name',
folderNameEmpty: 'Folder name cannot be empty',
folderCreatedSuccess: 'Folder created!',
folderAlreadyExists: 'A folder with this name already exists',
folderCreateError: 'Failed to create folder. Please try again.',
uploadSuccess: 'Files uploaded successfully',
uploadedTitle: 'Uploaded!',
Expand Down Expand Up @@ -1203,6 +1205,8 @@ const translations = {
folderNameLabel: 'Nombre',
folderNamePlaceholder: 'Nombre de carpeta',
folderNameEmpty: 'El nombre no puede estar vacío',
folderCreatedSuccess: '¡Carpeta creada!',
folderAlreadyExists: 'Ya existe una carpeta con este nombre',
folderCreateError: 'Error al crear la carpeta. Inténtalo de nuevo.',
uploadSuccess: 'Archivos subidos correctamente',
uploadedTitle: '¡Subido!',
Expand Down
228 changes: 114 additions & 114 deletions ios/Internxt.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions ios/Internxt/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ public class AppDelegate: ExpoAppDelegate {
deleteFromSharedKeychain(key: "shared_bridgeUser", accessGroup: sharedGroup)
deleteFromSharedKeychain(key: "shared_userId", accessGroup: sharedGroup)
}

if privateKeychainItemExists(key: "themePreference") {
copyToSharedKeychain(privateKey: "themePreference", sharedKey: "shared_themePreference", accessGroup: sharedGroup)
} else {
deleteFromSharedKeychain(key: "shared_themePreference", accessGroup: sharedGroup)
}
}

private func privateKeychainItemExists(key: String) -> Bool {
Expand Down
8 changes: 4 additions & 4 deletions ios/InternxtShareExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@
<key>ShareExtensionBackgroundColor</key>
<dict>
<key>alpha</key>
<integer>1</integer>
<integer>0</integer>
<key>blue</key>
<integer>255</integer>
<integer>0</integer>
<key>green</key>
<integer>255</integer>
<integer>0</integer>
<key>red</key>
<integer>255</integer>
<integer>0</integer>
</dict>
<key>UIAppFonts</key>
<array/>
Expand Down
13 changes: 7 additions & 6 deletions ios/InternxtShareExtension/ShareExtensionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ class ShareExtensionViewController: UIViewController {
var initialProps = sharedData ?? [:]
// ── Internxt: inject auth state from Keychain ───────────────────────────
if let sharedGroup = Bundle.main.object(forInfoDictionaryKey: "SharedKeychainGroup") as? String {
initialProps["photosToken"] = readFromSharedKeychain(key: "shared_photosToken", accessGroup: sharedGroup)
initialProps["mnemonic"] = readFromSharedKeychainStripped(key: "shared_mnemonic", accessGroup: sharedGroup)
initialProps["rootFolderId"] = readFromSharedKeychainStripped(key: "shared_rootFolderId", accessGroup: sharedGroup)
initialProps["bucket"] = readFromSharedKeychainStripped(key: "shared_bucket", accessGroup: sharedGroup)
initialProps["bridgeUser"] = readFromSharedKeychainStripped(key: "shared_bridgeUser", accessGroup: sharedGroup)
initialProps["userId"] = readFromSharedKeychainStripped(key: "shared_userId", accessGroup: sharedGroup)
initialProps["photosToken"] = readFromSharedKeychain(key: "shared_photosToken", accessGroup: sharedGroup)
initialProps["mnemonic"] = readFromSharedKeychainStripped(key: "shared_mnemonic", accessGroup: sharedGroup)
initialProps["rootFolderId"] = readFromSharedKeychainStripped(key: "shared_rootFolderId", accessGroup: sharedGroup)
initialProps["bucket"] = readFromSharedKeychainStripped(key: "shared_bucket", accessGroup: sharedGroup)
initialProps["bridgeUser"] = readFromSharedKeychainStripped(key: "shared_bridgeUser", accessGroup: sharedGroup)
initialProps["userId"] = readFromSharedKeychainStripped(key: "shared_userId", accessGroup: sharedGroup)
initialProps["themePreference"] = readFromSharedKeychainStripped(key: "shared_themePreference", accessGroup: sharedGroup)
}
// ── From expo-share-extension library ──────────────────────────────────
let currentBounds = self.view.bounds
Expand Down
1 change: 1 addition & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const untranspiledModulePatterns = [
'uuid',
'p-limit',
'yocto-queue',
'mime',
];

const config: Config.InitialOptions = {
Expand Down
12 changes: 10 additions & 2 deletions src/contexts/Theme/Theme.context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncStorageService from '@internxt-mobile/services/AsyncStorageService';
import { logger } from '@internxt-mobile/services/common';
import secureStorageService from '@internxt-mobile/services/SecureStorageService';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Appearance, NativeEventSubscription } from 'react-native';

Expand All @@ -22,10 +23,16 @@ interface ThemeProviderProps {
*/
const loadThemePreference = async (): Promise<ThemeMode> => {
try {
const savedTheme = await asyncStorageService.getThemePreference();
logger.info(`Saved theme from storage: ${savedTheme}`);
// SecureStorage is the source of truth after migration, was added to share with iOS in the keychain
const secureTheme = await secureStorageService.getItem('themePreference');
if (secureTheme === 'light' || secureTheme === 'dark') {
logger.info(`Loaded theme from SecureStorage: ${secureTheme}`);
return secureTheme;
}

const savedTheme = await asyncStorageService.getThemePreference();
if (savedTheme) {
logger.info(`Loaded theme from AsyncStorage: ${savedTheme}`);
return savedTheme;
}

Expand Down Expand Up @@ -135,6 +142,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {

applyTheme(newTheme, setThemeState);
await asyncStorageService.saveThemePreference(newTheme);
secureStorageService.setItem('themePreference', newTheme).catch(() => undefined);

setTimeout(() => {
isSettingThemeRef.current = false;
Expand Down
2 changes: 1 addition & 1 deletion src/services/AsyncStorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { AsyncStorageKey } from '../types';
import secureStorageService from './SecureStorageService';

const SENSITIVE_KEYS = [AsyncStorageKey.Token, AsyncStorageKey.PhotosToken, AsyncStorageKey.User];
const SENSITIVE_KEYS = [AsyncStorageKey.Token, AsyncStorageKey.PhotosToken, AsyncStorageKey.User, AsyncStorageKey.ThemePreference];

class AsyncStorageService {
private isSensitiveKey(key: AsyncStorageKey): boolean {
Expand Down
9 changes: 5 additions & 4 deletions src/shareExtension/ShareExtensionView.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { useShareExtension } from './hooks/useShareExtension.android';
import { useShareUpload } from './hooks/useShareUpload';
import { DriveScreen } from './screens/DriveScreen';
import { NotSignedInScreen } from './screens/NotSignedInScreen';
import { colors } from './theme';
import { useShareColors } from './theme';

const ShareExtensionView = ({ navigation, route }: RootStackScreenProps<'AndroidShare'>) => {
const tailwind = useTailwind();
const colors = useShareColors();
const { status, rootFolderUuid, sharedFiles, mnemonic, bucket, bridgeUser, userId } = useShareExtension(
route.params?.files ?? [],
);
Expand Down Expand Up @@ -52,15 +53,15 @@ const ShareExtensionView = ({ navigation, route }: RootStackScreenProps<'Android

if (status === 'unauthenticated') {
return (
<SafeAreaView style={tailwind('flex-1 bg-white')}>
<SafeAreaView style={[tailwind('flex-1'), { backgroundColor: colors.surface }]}>
<NotSignedInScreen onClose={handleClose} onOpenLogin={handleOpenLogin} />
</SafeAreaView>
);
}

if (status === 'loading' || !rootFolderUuid) {
return (
<SafeAreaView style={tailwind('flex-1 bg-white')}>
<SafeAreaView style={[tailwind('flex-1'), { backgroundColor: colors.surface }]}>
<View style={tailwind('flex-1 items-center justify-center')}>
<ActivityIndicator size="large" color={colors.primary} />
</View>
Expand All @@ -69,7 +70,7 @@ const ShareExtensionView = ({ navigation, route }: RootStackScreenProps<'Android
}

return (
<SafeAreaView style={tailwind('flex-1 bg-white')}>
<SafeAreaView style={[tailwind('flex-1'), { backgroundColor: colors.surface }]}>
<DriveScreen
sharedFiles={sharedFiles}
rootFolderUuid={rootFolderUuid}
Expand Down
57 changes: 31 additions & 26 deletions src/shareExtension/ShareExtensionView.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { useShareExtension } from './hooks/useShareExtension.ios';
import { useShareUpload } from './hooks/useShareUpload';
import { DriveScreen } from './screens/DriveScreen';
import { NotSignedInScreen } from './screens/NotSignedInScreen';
import { colors } from './theme';
import { ShareThemeProvider } from './ShareThemeProvider';
import { useShareColors } from './theme';

interface ShareExtensionProps {
photosToken?: string;
Expand All @@ -16,6 +17,7 @@ interface ShareExtensionProps {
bucket?: string;
bridgeUser?: string;
userId?: string;
themePreference?: 'light' | 'dark';
files?: string[];
images?: string[];
videos?: string[];
Expand All @@ -30,11 +32,13 @@ const ShareExtensionView = ({
bucket,
bridgeUser,
userId,
themePreference,
files,
images,
videos,
}: ShareExtensionProps) => {
const tailwind = useTailwind();
const colors = useShareColors();
const { sdkReady, sharedFiles } = useShareExtension({
photosToken,
mnemonic,
Expand Down Expand Up @@ -71,36 +75,37 @@ const ShareExtensionView = ({
close();
}, []);

if (!photosToken) {
return <NotSignedInScreen onClose={close} onOpenLogin={() => openHostApp(AppPaths.signIn())} />;
}

if (!sdkReady || !rootFolderId) {
return (
<View style={tailwind('flex-1 items-center justify-center bg-white')}>
let content;
if (photosToken && sdkReady && rootFolderId) {
content = (
<DriveScreen
sharedFiles={sharedFiles}
rootFolderUuid={rootFolderId}
uploadStatus={uploadStatus}
uploadErrorType={uploadErrorType}
uploadError={uploadError}
uploadProgress={uploadProgress}
thumbnailUri={thumbnailUri}
uploadedCount={uploadedCount}
collisionState={collisionState}
onClose={close}
onSave={handleSave}
onViewInFolder={handleViewInFolder}
onDismissError={resetUpload}
onCollisionAction={handleCollisionAction}
/>
);
} else if (photosToken) {
content = (
<View style={[tailwind('flex-1 items-center justify-center'), { backgroundColor: colors.surface }]}>
<ActivityIndicator size="large" color={colors.primary} />
</View>
);
} else {
content = <NotSignedInScreen onClose={close} onOpenLogin={() => openHostApp(AppPaths.signIn())} />;
}

return (
<DriveScreen
sharedFiles={sharedFiles}
rootFolderUuid={rootFolderId}
uploadStatus={uploadStatus}
uploadErrorType={uploadErrorType}
uploadError={uploadError}
uploadProgress={uploadProgress}
thumbnailUri={thumbnailUri}
uploadedCount={uploadedCount}
collisionState={collisionState}
onClose={close}
onSave={handleSave}
onViewInFolder={handleViewInFolder}
onDismissError={resetUpload}
onCollisionAction={handleCollisionAction}
/>
);
return <ShareThemeProvider themePreference={themePreference}>{content}</ShareThemeProvider>;
};

export default ShareExtensionView;
16 changes: 16 additions & 0 deletions src/shareExtension/ShareThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createContext, useContext, type ReactNode } from 'react';

export type ThemePreference = 'light' | 'dark' | null | undefined;

export const ShareThemeContext = createContext<ThemePreference>(undefined);

interface ShareThemeProviderProps {
themePreference: ThemePreference;
children: ReactNode;
}

export const ShareThemeProvider = ({ themePreference, children }: ShareThemeProviderProps) => (
<ShareThemeContext.Provider value={themePreference}>{children}</ShareThemeContext.Provider>
);

export const useShareThemeContext = (): ThemePreference => useContext(ShareThemeContext);
Loading
Loading