Commit Graph

68 Commits

Author SHA1 Message Date
Kyle Bolen
fd5753382f Rewrite BrowseScreen: unified scroll with folder sections + jump list
- Single LazyColumn with folder divider headers embedded — no page switching
- Left panel is a jump list: click scrolls to that folder, highlights
  active folder as you scroll (derivedStateOf scroll spy)
- Each folder section header has Select All / Deselect All buttons
- Each day sub-header has Select Day / Deselect Day toggle
- Removed 'All Photos' merged view — folders are always separate sections
- PhotoThumb moved inline, cleaned up imports
2026-07-28 05:24:29 +00:00
Kyle Bolen
f6aede99f1 Fix device name, ImportScreen layout, and preserve folder default
- AndroidConnector: use model: field for display name instead of
  product: (which is a cryptic codename like 'o1quew' on Samsung)
- ImportScreen: clean up layout with ToggleRow helper, remove duplicate
  toggles, all 4 options in one Options section
- AppState: preserveFolderStructure defaults to true
2026-07-28 05:20:02 +00:00
Kyle Bolen
409cb3b4d1 Fix doubled photos: /sdcard is a symlink to /storage/emulated/0
Scanning both paths listed every photo twice. Now only scan
/storage/emulated/0/DCIM which is the canonical path.
2026-07-28 05:08:32 +00:00
Kyle Bolen
0e6f0d9c7e Rewrite BrowseScreen: folder panel + day-grouped photo grid
New two-panel layout:
- Left (200dp): FolderPanel lists all DCIM subfolders with photo count
  and date range. Camera-roll folders highlighted separately from other
  folders (Screenshots, Video Editor, etc.). Selection badge per folder.
- Right: DayGroupedGrid groups photos by date with day headers. Each day
  header shows count and a 'Select day' / 'Deselect day' toggle.
  Photos displayed in rows of 4 with thumbnails.

PhonePhoto now carries sourceFolder name. AndroidConnector tags each
photo with its DCIM subfolder. AppState manages selectedFolder and
visiblePhotos StateFlows. ExifFilter.isCameraRollFolder() classifies
folders as camera-roll vs other.

Preserves all existing functionality: thumbnails, import flow, HEIC
warning, selection.
2026-07-28 05:05:21 +00:00
Kyle Bolen
43086a91f1 Thumbnails: pull full file + sips resize, max 3 concurrent ADB pulls
MediaStore content query is unreliable on Samsung. Switch to:
1. adb pull full file to tmp
2. sips --resampleWidth 240 to generate thumbnail
3. Cache in ~/.photophetch/thumbcache/<serial>/
4. Semaphore(3) limits concurrent pulls — cells scrolled past are
   cancelled by Compose LaunchedEffect lifecycle automatically.

First load per photo takes 2-3s. Subsequent views instant from cache.
2026-07-28 04:57:13 +00:00
Kyle Bolen
301291616d Fix thumbnails: use MediaStore thumbnail DB instead of partial file pull
Previous approach pulled first 64KB of JPEG which produces a truncated
file Skia cannot decode. New approach:
1. Query MediaStore content:// URI to find the pre-generated thumbnail
   path for each photo (Android generates these automatically)
2. Pull just the thumbnail file (~20-50KB vs 3-5MB full file)
3. Cache locally — subsequent views are instant

Falls back gracefully (placeholder icon) if MediaStore query fails.
2026-07-28 04:49:20 +00:00
Kyle Bolen
770aa58dd0 Fix listing speed + add thumbnails
AndroidConnector: replace per-file ls -la with single directory ls -la
call — one ADB round trip per folder instead of one per file. Much
faster for large Camera folders.

BrowseScreen: add thumbnail loading via ThumbnailCache. Pulls first
64KB of each JPEG via adb exec-out dd (enough for embedded EXIF thumb),
caches to ~/.photophetch/thumbcache/<serial>/. Loads asynchronously
on Dispatchers.IO so UI stays responsive. Placeholder icon shown until
thumbnail arrives.
2026-07-28 04:40:42 +00:00
Kyle Bolen
ec114aa7ce Fix Android file listing: replace stat with ls -la for compatibility
Samsung (and other Android vendors using toybox) do not support GNU
stat -c flag syntax. Switch to find for file discovery + ls -la per
file for size/date metadata, which works on all Android versions.
2026-07-28 04:36:40 +00:00
Kyle Bolen
3da4e464d2 Add Show All mode with optional folder structure preservation
- BrowseScreen: Show All toggle chip in toolbar bypasses ExifFilter
  and shows every file from every DCIM subfolder
- ImportScreen: Preserve folder structure toggle (auto-enabled in Show
  All mode) — copies files into Camera/, Screenshots/ etc subfolders
  under the staging destination instead of flat
- AppState: _showAll StateFlow, toggleShowAll(), loadPhotos and
  setDateFilter respect the flag
- Copier: extracts DCIM subfolder from devicePath and creates matching
  subdirectory in staging when preserveFolderStructure=true
2026-07-28 04:28:17 +00:00
Kyle Bolen
ff812a9550 Tighten ExifFilter: exclude Screen Recordings, Videocaptures, Resize folders 2026-07-28 04:25:25 +00:00
Kyle Bolen
af36ae5663 Fix Android photo discovery: dynamically scan all DCIM subdirs
Instead of hardcoding /sdcard/DCIM/Camera, list all subdirectories
under /sdcard/DCIM and /storage/emulated/0/DCIM dynamically. This
handles Samsung, Pixel, OnePlus and other vendors that use different
DCIM subfolder names. Falls back to hardcoded list if discovery finds
nothing.
2026-07-28 04:22:23 +00:00
Kyle Bolen
5b17d28f66 docs: document PTPCamera + AMPDeviceDiscoveryAgent conflict and fix 2026-07-28 03:56:54 +00:00
Kyle Bolen
35b98e1857 Auto-increment folder name if staging folder already exists
StagingPath.uniqueFolderName() checks whether the base folder name is
already taken and appends _2, _3, etc. until a free name is found.
AppState.prepareImport() now uses this so the ImportScreen always
pre-fills a unique folder name.
2026-07-28 03:48:30 +00:00
Kyle Bolen
05897e6d62 Add native macOS folder picker for staging directory
Uses AWT FileDialog with apple.awt.fileDialogForDirectories=true to open
the native macOS folder chooser. Falls back gracefully (dialog cancelled)
with no change to the text field.
2026-07-28 03:47:31 +00:00
Kyle Bolen
105077baba docs: clarify Android USB mode selection (Transferring files, not PTP) 2026-07-28 03:44:19 +00:00
Kyle Bolen
bbc764dbcb Fix Gradle toolchain resolution on macOS/Corretto
Replace jvmToolchain(17) with jvmToolchain(21) + explicit kotlinOptions
jvmTarget=17 + java sourceCompatibility/targetCompatibility. Avoids
toolchain provisioning errors when the JDK is present but not registered
via the Gradle toolchain API.
2026-07-28 03:38:02 +00:00
Kyle Bolen
c4608e9c0e 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
2026-07-28 03:09:28 +00:00
Kyle Bolen
145b7bc955 Initial commit: PhotoPhetch v1.0.0
Kotlin/Compose Multiplatform desktop app for importing photos from
phones into PhotoPhile staging directory.

Features:
- Android support via ADB subprocess
- iPhone support via gphoto2 subprocess
- Smart filtering: excludes screenshots, received images, Live Photo MOVs
- HEIC -> JPEG conversion via macOS sips (built-in)
- SHA-256 verification of every copied file
- Safe delete with explicit confirmation
- Staging path convention: _staging/{date}_{device}/
- Config persistence at ~/.photophetch/config.json
2026-07-28 03:04:05 +00:00