Commit Graph

23 Commits

Author SHA1 Message Date
Kyle Bolen
7aa7e4dae1 VerifyScreen: top + bottom scroll shadows on results list
Top shadow: fades in as you scroll down from the top (content hidden above).
Bottom shadow: present when last item is not yet visible (content below).
Both use drawWithContent overlay at 8% black opacity, 24dp strip.
2026-07-28 05:36:55 +00:00
Kyle Bolen
87241ba1d4 ImportScreen: shadow fades out over last 80px of scroll
shadowAlpha = (remaining / 80).coerceIn(0,1) — full strength when
far from bottom, smoothly fades to zero over the last 80px.
2026-07-28 05:34:38 +00:00
Kyle Bolen
8306aa2729 ImportScreen: replace fade with bottom edge shadow
24dp shadow strip on the bottom edge of the scroll area when more
content is below. Disappears when scrolled to bottom. Matches how
Material dialogs and macOS sheets indicate overflow.
2026-07-28 05:33:17 +00:00
Kyle Bolen
9c489306f4 ImportScreen: fade gradient indicates more content below
derivedStateOf tracks scrollState.value < maxValue. When not at
bottom, a 48dp white-to-transparent gradient overlays the bottom
of the scroll area. Disappears when fully scrolled.
2026-07-28 05:31:39 +00:00
Kyle Bolen
edaf4cb0d7 ImportScreen: scrollable content with pinned bottom button
- Body scrolls via verticalScroll
- Header and Start Import button stay fixed outside the scroll
- Summary card now shows source folder breakdown for multi-folder imports
- HorizontalDivider separates pinned button from scroll area
2026-07-28 05:29:17 +00:00
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