Fix compile errors from initial build
- Main.kt: move ConfigStore/AppState outside application {} scope (remember
is not valid outside a composable)
- IosConnector: split runGphoto2/runGphoto2InDir to avoid named+positional
vararg mixing (Kotlin does not allow this)
- ConnectScreen: fix SmartPhone -> Smartphone icon name
- ConnectScreen/BrowseScreen/ImportScreen/SettingsScreen: update deprecated
Icons.Default.ArrowBack to Icons.AutoMirrored.Filled.ArrowBack
- AndroidConnector: remove unused variable assignment in remoteChecksum
- ExifFilter: replace return@filter labels with private helper function to
eliminate ambiguous label warnings
This commit is contained in:
@@ -9,15 +9,19 @@ import com.bolenpad.photophetch.ui.App
|
||||
import com.bolenpad.photophetch.ui.AppState
|
||||
import com.bolenpad.photophetch.util.ConfigStore
|
||||
|
||||
fun main() = application {
|
||||
val configStore = remember { ConfigStore() }
|
||||
val appState = remember { AppState(configStore) }
|
||||
fun main() {
|
||||
// ConfigStore and AppState live outside the composition — they are
|
||||
// application-lifetime singletons, not composable state.
|
||||
val configStore = ConfigStore()
|
||||
val appState = AppState(configStore)
|
||||
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "PhotoPhetch",
|
||||
state = WindowState(size = DpSize(900.dp, 700.dp)),
|
||||
) {
|
||||
App(appState)
|
||||
application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "PhotoPhetch",
|
||||
state = WindowState(size = DpSize(900.dp, 700.dp)),
|
||||
) {
|
||||
App(appState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ class AndroidConnector(
|
||||
// Android has md5sum or sha256sum depending on version; try sha256sum first
|
||||
var output = runAdb(serial, "shell", "sha256sum", photo.devicePath)
|
||||
if (output.contains("not found") || output.isBlank()) {
|
||||
output = runAdb(serial, "shell", "md5sum", photo.devicePath)
|
||||
runAdb(serial, "shell", "md5sum", photo.devicePath)
|
||||
// md5sum isn't SHA-256 — return null so Copier uses its own verification
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ class IosConnector(
|
||||
val destFile = destinationDir.resolve(photo.filename)
|
||||
|
||||
// --get-file downloads to current dir; we set working dir to destinationDir
|
||||
val output = runGphoto2(
|
||||
workingDir = destinationDir,
|
||||
val output = runGphoto2InDir(
|
||||
destinationDir,
|
||||
"--port", port,
|
||||
"--get-file", fileIndex,
|
||||
"--filename", photo.filename,
|
||||
@@ -225,7 +225,9 @@ class IosConnector(
|
||||
private fun requirePort(device: DeviceInfo): String =
|
||||
device.gphotoPort ?: throw DeviceException("No gphoto2 port for device: ${device.displayName}")
|
||||
|
||||
private fun runGphoto2(vararg args: String, workingDir: Path? = null): String {
|
||||
private fun runGphoto2(vararg args: String): String = runGphoto2InDir(null, *args)
|
||||
|
||||
private fun runGphoto2InDir(workingDir: Path?, vararg args: String): String {
|
||||
val cmd = buildList {
|
||||
add(gphoto2Bin)
|
||||
addAll(args)
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
||||
@@ -133,7 +133,7 @@ private fun BrowseTopBar(
|
||||
title = { Text(device?.displayName ?: "Browse Photos") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PhoneAndroid
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.SmartPhone
|
||||
import androidx.compose.material.icons.filled.Smartphone
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -153,7 +153,7 @@ private fun DeviceCard(device: DeviceInfo, onClick: () -> Unit) {
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
if (device.type == DeviceType.IOS) Icons.Default.SmartPhone else Icons.Default.PhoneAndroid,
|
||||
if (device.type == DeviceType.IOS) Icons.Default.Smartphone else Icons.Default.PhoneAndroid,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.bolenpad.photophetch.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.FolderOpen
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
@@ -36,7 +36,7 @@ fun ImportScreen(state: AppState) {
|
||||
// Header
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = { state.navigateTo(Screen.BROWSE) }) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
Text("Import Configuration", style = MaterialTheme.typography.headlineSmall)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.bolenpad.photophetch.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -24,7 +24,7 @@ fun SettingsScreen(state: AppState) {
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = { state.navigateTo(Screen.CONNECT) }) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
Text("Settings", style = MaterialTheme.typography.headlineSmall)
|
||||
}
|
||||
|
||||
@@ -58,33 +58,30 @@ object ExifFilter {
|
||||
photos: List<PhonePhoto>,
|
||||
stripLivePhotoMotion: Boolean = true,
|
||||
): List<PhonePhoto> {
|
||||
return photos.filter { photo ->
|
||||
// Always exclude Live Photo motion clips if requested
|
||||
if (stripLivePhotoMotion && photo.isLivePhotoMotion) return@filter false
|
||||
return photos.filter { photo -> isCameraRollOriginal(photo, stripLivePhotoMotion) }
|
||||
}
|
||||
|
||||
// Exclude if the device path contains a known non-camera directory
|
||||
val pathLower = photo.devicePath.lowercase()
|
||||
if (excludedPathFragments.any { fragment -> pathLower.contains(fragment) }) {
|
||||
return@filter false
|
||||
}
|
||||
private fun isCameraRollOriginal(photo: PhonePhoto, stripLivePhotoMotion: Boolean): Boolean {
|
||||
// Always exclude Live Photo motion clips if requested
|
||||
if (stripLivePhotoMotion && photo.isLivePhotoMotion) return false
|
||||
|
||||
// Exclude if filename starts with a screenshot prefix
|
||||
val filenameLower = photo.filename.lowercase()
|
||||
if (screenshotFilePrefixes.any { prefix -> filenameLower.startsWith(prefix) }) {
|
||||
return@filter false
|
||||
}
|
||||
// Exclude if the device path contains a known non-camera directory
|
||||
val pathLower = photo.devicePath.lowercase()
|
||||
if (excludedPathFragments.any { fragment -> pathLower.contains(fragment) }) return false
|
||||
|
||||
// Exclude files with no EXIF Make/Model AND not in a DCIM path
|
||||
// (indicates received image or screenshot — not a camera original)
|
||||
val hasExif = photo.exifMake != null || photo.exifModel != null
|
||||
val inDcim = pathLower.contains("dcim")
|
||||
if (!hasExif && !inDcim) return@filter false
|
||||
// Exclude if filename starts with a screenshot prefix
|
||||
val filenameLower = photo.filename.lowercase()
|
||||
if (screenshotFilePrefixes.any { prefix -> filenameLower.startsWith(prefix) }) return false
|
||||
|
||||
// Skip files with completely unknown media type
|
||||
if (photo.mediaType == MediaType.UNKNOWN) return@filter false
|
||||
// Exclude files with no EXIF Make/Model AND not in a DCIM path
|
||||
val hasExif = photo.exifMake != null || photo.exifModel != null
|
||||
val inDcim = pathLower.contains("dcim")
|
||||
if (!hasExif && !inDcim) return false
|
||||
|
||||
true
|
||||
}
|
||||
// Skip files with completely unknown media type
|
||||
if (photo.mediaType == MediaType.UNKNOWN) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user