Commit Graph

7 Commits

Author SHA1 Message Date
agra
d68a2978eb ux: bulk WIP — UxPlugin→XPlugin rename + new anim/core/navi/reactive packages
Catch-all commit for outstanding pre-existing local changes. Mixes
several themes that would normally be split:

- Rename: UxPlugin → XPlugin across iOS, macOS, Android registrants.
- New top-level packages under lib/src/: anim/ (animated values,
  panes, sheets, dock, measured), core/ (Emitter, ReactiveBuilder
  scaffolding, presenter/widget/value/dispose primitives), navi/
  (Screen/ScreenStack/Router/hero/transitions), reactive/.
- Edits across existing plugins (clipboard, crash, file, gallery,
  keyboard, scanner, sensor, url) to align with the new core.
- Test updates and CHANGELOG/README touches accompanying the above.
2026-05-21 08:58:07 +03:00
agra
71c84179a6 camera: drop NSLog scaffolding now that macOS rotation is settled
The two `NSLog("[ux.camera] …")` calls were debug instrumentation for
diagnosing the macOS photo rotation issue. The bug is fixed
(macOS pinned to 0° rotation, photo + preview + video all 1280x720
landscape), so the NSLog calls are now stderr noise on every shot.

Keep the per-shot `diag(photo: WxH …)` emit since it goes through
`ux.Log` (gated by level, lands in banlu.jsonl) — useful if rotation
ever regresses on a different camera / macOS version, and the cost is
one log line per photo capture.
2026-05-13 21:28:05 +03:00
agra
e03e698caa camera: NSLog fallback in photo delegate so we can see it fired
User reports macOS photo still rotated AND no `photo:` line in
banlu.jsonl — so either the delegate isn't firing at all or the
event-channel path is broken on the success branch.

Add `NSLog` at the top of `photoOutput(_:didFinishProcessingPhoto:error:)`
and on the CGImage path. NSLog lands in `flutter run` stderr +
macOS Console regardless of channel state, so even if the diag
event drops we'll see the delegate firing + the CGImage's
dimensions. Once we see them we'll know whether it's a delegate
problem or a channel problem.
2026-05-13 20:36:22 +03:00
agra
9ba8ff8e61 camera: macOS uses cgImageRepresentation + ImageIO (skip fileDataRepresentation EXIF)
Per Apple's AVCaptureSession.h docs (line 1106), AVCapturePhotoOutput
applies connection rotation via EXIF tags rather than physical pixel
rotation. The macOS variants of `fileDataRepresentationWithCustomizer`
and `fileDataRepresentationWithReplacementMetadata` are
API_UNAVAILABLE(macos), so we can't replace the embedded EXIF
through the standard customizer.

Workaround: on macOS, grab the raw `AVCapturePhoto.cgImageRepresentation()`
— Apple documents this as "the physical rotation of the CGImageRef
matches that of the main image. Exif orientation has not been
applied." — and re-encode the JPEG via `CGImageDestination` with no
orientation metadata. Resulting JPEG has sensor-native landscape
pixels and no EXIF Orientation tag; viewers and Flutter's image
codec both display as landscape.

iOS path unchanged (still uses fileDataRepresentation).

Diagnostic now fires on both the success and failure branches of
the delegate, so when the capture fails (e.g. the macOS
"OSStatus error 13" the user observed) the failure reason — domain,
code, localized description — is captured to banlu.jsonl instead
of silently dropped.
2026-05-13 20:32:14 +03:00
agra
cb2b57f661 camera: log captured JPEG dims + EXIF Orientation post-shot
Per Apple's macOS AVCaptureSession.h docs (line 1106), setting
`videoRotationAngle` on `AVCapturePhotoOutput`'s connection
"does not necessarily result in physical rotation of video buffers
… In the AVCapturePhotoOutput, orientation is handled using Exif
tags." So our connection-rotation tweaks only affect the EXIF
Orientation tag the JPEG carries — pixel data is sensor-native.

Yet the user keeps seeing a rotated JPEG even after `stripJpegApp1`
removes APP1 (EXIF). So either the pixel buffer IS rotated despite
the docs, or EXIF is in a non-APP1 marker, or Flutter's decoder
auto-rotates somehow.

Log the actual captured JPEG's dimensions + EXIF Orientation to
banlu.jsonl via the existing per-handle diagnostic stream:
`CGImageSourceCopyPropertiesAtIndex` reads
`kCGImagePropertyPixelWidth/Height/Orientation` from the JPEG
bytes that `AVCapturePhoto.fileDataRepresentation()` produces.

Format: `photo: WxH landscape|portrait exifOrientation=N`.

Once we see what AVCapturePhotoOutput is actually producing on the
user's Mac we'll know which side of the pipeline to fix.
2026-05-13 20:26:24 +03:00
agra
8ab672c12a camera: per-platform capture-orientation extension + macOS sensor=0
macOS preview was stretching (aspect wrong) and macOS photo capture
was rotating the landscape sensor 90° because the shared
PhotoOutput / CameraInstance code was setting
`AVCaptureConnection.videoOrientation` from the orientation snapshot
unconditionally. iOS needs that to rotate sample buffers to portrait;
macOS desktop cams are physically landscape and any rotation just
skews the result.

Moved the rotation call behind a per-platform extension on
`AVCaptureConnection`:
  - `ios/Classes/Camera/AVCaptureConnection+iOS.swift` applies the
    snapshot orientation (current behavior).
  - `macos/Classes/Camera/AVCaptureConnection+macOS.swift` is a
    no-op. macOS-flavoured photos / preview frames now flow at
    native landscape orientation.

`CaptureDevice` reports sensorOrientation=0 on macOS (was hardcoded
90 for iOS); on macOS the page's `normalizeCameraCapture` math then
collapses to identity and the saved JPEG stays the landscape the
sensor produced. iOS keeps sensorOrientation=90 (matches
camera_avfoundation's reported value and the existing capture-
transform math).

Photo and video paths now both produce upright content on macOS
(video already worked because VideoRecorder's transform table maps
the always-portraitUp macOS snapshot to `.identity`).
2026-05-13 19:07:29 +03:00
agra
14565ebd7a camera: macOS port via darwin/ split (no shared-file pragmas)
Reuse the AVFoundation Swift files between iOS and macOS without
sprinkling `#if canImport(UIKit)` through them. The split is:

  darwin/Camera/                 platform-shared (AVFoundation only)
    CameraPlugin                 channel + instance map
    CameraInstance               session + outputs + texture
    CameraSession                AVCaptureSession + runtime-error obs
    CaptureDevice                front/back discovery
    PhotoOutput                  AVCapturePhotoOutput
    PreviewSink                  CVPixelBuffer → FlutterTexture
    VideoRecorder                AVAssetWriter
    DeviceOrientation            wire-string enum

  ios/Classes/Camera/            iOS-only impls + extensions
    AudioSession                 AVAudioSession.upgradeForRecording
    DeviceOrientationBridge      UIDevice.orientation listener
    CameraSession+iOS            AVCaptureSessionWasInterrupted obs
                                 + InterruptionReason decode + the
                                 application-audio-session flags
                                 (all iOS-only on AVCaptureSession)
    CameraSettings               UIApplication.openSettingsURLString
    FlutterRegistrar+iOS         method-form of textures/messenger

  macos/Classes/Camera/          macOS no-op stubs (same surface)
    AudioSession                 no-op (no AVAudioSession on macOS)
    DeviceOrientationBridge      no-op (desktops don't rotate)
    CameraSession+macOS          no-op setupPlatform()
    CameraSettings               NSWorkspace → System Settings'
                                 Privacy_Camera pane
    FlutterRegistrar+macOS       property-form of textures/messenger

`CameraSession.init` now calls `setupPlatform()` which each platform
provides via an extension — keeps the iOS-only interruption observer
and the `automaticallyConfiguresApplicationAudioSession` /
`usesApplicationAudioSession` flags (both iOS-only on AVCaptureSession)
out of the shared file. Flash-mode in PhotoOutput uses
`if #available(macOS 11/13, *)` rather than `#if`, since those are
plain version gates not platform splits.

The shared files compile into the iOS pod from `ios/Classes/Camera-shared/`
and into the macOS pod from `macos/Classes/Camera-shared/`, each a
mirror populated by a `prepare_command` in the podspec:

    rm -rf Classes/Camera-shared && cp -R ../darwin/Camera Classes/Camera-shared

Symlinks and `../` source globs both fail — Pathname.glob bails on
symlinks, and CocoaPods silently drops paths that escape the pod
directory. The mirror destinations are .gitignore'd.

macOS UxPlugin now registers CameraPlugin alongside the others.
2026-05-13 18:53:46 +03:00