Skip to content
Open
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
9 changes: 6 additions & 3 deletions mobile-app/src/screens/AddDeviceModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Platform,
StyleSheet,
Switch,
Text,
Expand Down Expand Up @@ -40,7 +41,7 @@ export function AddDeviceModal({ visible, onClose, onAdded, initialDeviceId }: P
// accept fresh pre-fills when reopened (e.g. successive QR scans)
if (visible && initialDeviceId) setDeviceId(initialDeviceId);
}, [visible, initialDeviceId]);
const [autoAcceptFolders, setAutoAcceptFolders] = useState(true);
const [autoAcceptFolders, setAutoAcceptFolders] = useState(Platform.OS !== 'android');
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [scannerOpen, setScannerOpen] = useState(false);
Expand Down Expand Up @@ -74,7 +75,7 @@ export function AddDeviceModal({ visible, onClose, onAdded, initialDeviceId }: P
const reset = () => {
setDeviceId('');
setName('');
setAutoAcceptFolders(true);
setAutoAcceptFolders(Platform.OS !== 'android');
setError(null);
setSubmitting(false);
};
Expand Down Expand Up @@ -184,7 +185,9 @@ export function AddDeviceModal({ visible, onClose, onAdded, initialDeviceId }: P
<View style={{ flex: 1, paddingRight: 12 }}>
<Text style={styles.switchLabel}>Auto-accept folders</Text>
<Text style={styles.switchHint}>
Skip the per-folder accept prompt for offers from this peer.
{Platform.OS === 'android'
? 'Skip the per-folder prompt. Folders land in app storage (Android/data), which other apps cannot open.'
: 'Skip the per-folder accept prompt for offers from this peer.'}
</Text>
</View>
<Switch
Expand Down
7 changes: 5 additions & 2 deletions mobile-app/src/screens/DevicesScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import {
Alert,
Platform,
RefreshControl,
ScrollView,
StyleSheet,
Expand Down Expand Up @@ -142,7 +143,7 @@ function PendingDeviceCard({
}) {
const client = useSyncthingClient();
const [busy, setBusy] = useState(false);
const [autoAcceptFolders, setAutoAcceptFolders] = useState(true);
const [autoAcceptFolders, setAutoAcceptFolders] = useState(Platform.OS !== 'android');

const accept = async () => {
if (busy) return;
Expand Down Expand Up @@ -205,7 +206,9 @@ function PendingDeviceCard({
<View style={{ flex: 1, paddingRight: 12 }}>
<Text style={styles.autoAcceptLabel}>Auto-accept folders</Text>
<Text style={styles.autoAcceptHint}>
Skip the per-folder accept prompt for offers from this peer.
{Platform.OS === 'android'
? 'Skip the per-folder prompt. Folders land in app storage (Android/data), which other apps cannot open.'
: 'Skip the per-folder accept prompt for offers from this peer.'}
</Text>
</View>
<Switch
Expand Down
101 changes: 101 additions & 0 deletions mobile-app/src/screens/FolderDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import { Focusable } from '../components/Focusable';
import { useKeyboardHeight } from '../hooks/useKeyboardHeight';
import { useSyncthing, useSyncthingClient } from '../daemon/SyncthingContext';
import type { DbStatus, DeviceConfig, FolderConfig, FolderError } from '../api/types';
import { isAbortError } from '../api/syncthing';
import { colors, formatBytes, Progress } from '../components/ui';
import { removeDir } from '../fs/bridgeFs';
import GoBridge from '../GoServerBridgeJSI';
import {
filesystemTypeForExternal,
isExternalFolder,
pickExternalFolderWithICloudWarning,
externalFolderNeedsAllFilesAccess,
Expand Down Expand Up @@ -401,6 +403,95 @@ export function FolderDetailModal({
}
};

const moveReady = !!status && status.state === 'idle' && status.needBytes === 0;

const doMove = async (newPath: string, displayName: string) => {
if (!folder) return;
setBusy(true);
setError(null);
const oldPath = folder.path;
try {
let ignores: string[] = [];
try {
ignores = await client.getIgnores(folder.id);
} catch {
// keep going; folder still usable without ignores
}
await client.deleteFolder(folder.id);
const fsType = filesystemTypeForExternal(newPath);
const usesSaf = fsType === 'saf';
const moved: FolderConfig = {
...folder,
path: newPath,
filesystemType: fsType,
rescanIntervalS: usesSaf ? 60 : folder.rescanIntervalS,
fsWatcherEnabled: !usesSaf && folder.fsWatcherEnabled,
};
try {
await client.putFolder(moved);
} catch (e) {
if (!isAbortError(e)) throw e;
const created = await client.waitForFolder(moved.id, { deadlineMs: 60_000 });
if (!created) throw new Error('Moving the folder timed out. Please try again.');
}
if (ignores.length > 0) {
try {
await client.setIgnores(moved.id, ignores);
} catch {
// best-effort
}
}
onChanged();
onClose();
if (isExternal) return;
Alert.alert(
'Folder moved',
`"${folder.label || folder.id}" now syncs to ${displayName}. Files will be re-downloaded from your other devices.\n\nDelete the old copy in app storage?`,
[
{ text: 'Keep', style: 'cancel' },
{
text: 'Delete old copy',
style: 'destructive',
onPress: () => {
try {
removeDir(oldPath);
} catch (e) {
Alert.alert('Could not delete', e instanceof Error ? e.message : String(e));
}
},
},
],
);
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setBusy(false);
}
};

const confirmMove = () => {
if (!folder) return;
if (!moveReady) {
Alert.alert(
'Folder not up to date',
'Wait until this folder shows "Up to date" before moving it, otherwise local changes that have not synced yet will be lost.',
);
return;
}
pickExternalFolderWithICloudWarning(picked => {
if (!picked) return;
const name = picked.displayName || 'the chosen folder';
Alert.alert(
'Move folder?',
`"${folder.label || folder.id}" will sync to ${name} instead of its current location.\n\nAll files will be re-downloaded from your other devices. The folder must stay shared with at least one connected device.`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Move', onPress: () => doMove(picked.path, name) },
],
);
});
};

const confirmDelete = () => {
if (!folder) return;
Alert.alert(
Expand Down Expand Up @@ -944,6 +1035,16 @@ export function FolderDetailModal({
</Focusable>
) : null}

<Focusable
style={[styles.actionBtn, styles.overrideBtn]}
onPress={confirmMove}
disabled={busy}
>
<Text style={[styles.actionBtnText, styles.overrideBtnText]}>
Move to another location
</Text>
</Focusable>

<Focusable
style={[styles.actionBtn, styles.deleteBtn, styles.deleteBtnFull]}
onPress={confirmDelete}
Expand Down
Loading