Fix EXIF verification for non-Apple devices + sticky preserve folder

EXIF verification: any non-blank Make is now considered verified.
Only absent/empty Make triggers 'Origin unknown' warning. Samsung,
Google, etc. are all legitimate camera makes.
Also cache exifMake to avoid calling exiftool twice per file.

Preserve folder structure: add defaultPreserveFolderStructure to
AppConfig. Toggling in ImportScreen saves it as the new default.
prepareImport() reads from config instead of always resetting to true.
This commit is contained in:
Kyle Bolen
2026-07-30 12:48:15 +00:00
parent a8906fa265
commit dc32a3a3d7
4 changed files with 17 additions and 8 deletions

View File

@@ -17,6 +17,8 @@ data class AppConfig(
val defaultStripLivePhotoMotion: Boolean = true, val defaultStripLivePhotoMotion: Boolean = true,
/** Default: do not delete from phone (safer default) */ /** Default: do not delete from phone (safer default) */
val defaultDeleteAfterVerify: Boolean = false, val defaultDeleteAfterVerify: Boolean = false,
/** Default: preserve folder structure on import */
val defaultPreserveFolderStructure: Boolean = true,
/** Whether the user has been warned about HEIC phone settings */ /** Whether the user has been warned about HEIC phone settings */
val heicWarningDismissed: Boolean = false, val heicWarningDismissed: Boolean = false,
/** Path to adb binary, or null to use PATH */ /** Path to adb binary, or null to use PATH */

View File

@@ -154,6 +154,8 @@ class Copier(
} }
} }
val exifMake = readExifMake(finalFile)
CopyResult( CopyResult(
photo = photo, photo = photo,
destinationPath = finalFile, destinationPath = finalFile,
@@ -161,11 +163,8 @@ class Copier(
destSha256 = destHash, destSha256 = destHash,
verified = verified, verified = verified,
convertedFromHeic = wasConverted, convertedFromHeic = wasConverted,
exifVerified = readExifMake(finalFile)?.let { make -> exifVerified = exifMake?.let { it.isNotBlank() },
make.contains("apple", ignoreCase = true) || exifMake = exifMake,
make.contains(photo.exifMake ?: "apple", ignoreCase = true)
},
exifMake = readExifMake(finalFile),
error = when { error = when {
!verified && remoteHash != null -> "Hash mismatch — file may be corrupt" !verified && remoteHash != null -> "Hash mismatch — file may be corrupt"
!verified -> "Copy failed — file missing or size mismatch" !verified -> "Copy failed — file missing or size mismatch"

View File

@@ -371,11 +371,11 @@ class AppState(private val configStore: ConfigStore) {
StagingPath.uniqueFolderName(Paths.get(cfg.stagingRoot), baseName) StagingPath.uniqueFolderName(Paths.get(cfg.stagingRoot), baseName)
} else baseName } else baseName
// Multi-folder selection → preserve structure by default // Multi-folder selection → preserve structure; otherwise use saved config default
val multipleSourceFolders = _selectedPhotos.value.map { it.sourceFolder }.distinct().size > 1 val multipleSourceFolders = _selectedPhotos.value.map { it.sourceFolder }.distinct().size > 1
_importConfig.update { it.copy( _importConfig.update { it.copy(
folderName = folderName, folderName = folderName,
preserveFolderStructure = multipleSourceFolders, preserveFolderStructure = if (multipleSourceFolders) true else cfg.defaultPreserveFolderStructure,
) } ) }
navigateTo(Screen.IMPORT_CONFIG) navigateTo(Screen.IMPORT_CONFIG)
} }
@@ -469,6 +469,11 @@ class AppState(private val configStore: ConfigStore) {
saveConfig() saveConfig()
} }
fun updateDefaultPreserveFolderStructure(value: Boolean) {
_config.update { it.copy(defaultPreserveFolderStructure = value) }
saveConfig()
}
fun dismissHeicWarning() { fun dismissHeicWarning() {
_config.update { it.copy(heicWarningDismissed = true) } _config.update { it.copy(heicWarningDismissed = true) }
saveConfig() saveConfig()

View File

@@ -154,7 +154,10 @@ fun ImportScreen(state: AppState) {
label = "Preserve folder structure", label = "Preserve folder structure",
description = "Files go into subfolders matching their source (Camera/, Screenshots/, etc.).", description = "Files go into subfolders matching their source (Camera/, Screenshots/, etc.).",
checked = importConfig.preserveFolderStructure, checked = importConfig.preserveFolderStructure,
onCheckedChange = { state.updateImportConfig { copy(preserveFolderStructure = it) } }, onCheckedChange = {
state.updateImportConfig { copy(preserveFolderStructure = it) }
state.updateDefaultPreserveFolderStructure(it)
},
) )
ToggleRow( ToggleRow(
label = "Delete from phone after verification", label = "Delete from phone after verification",