Files
ux/ios/Classes/Camera/CameraInstance+iOS.swift
agra 3d36f17edf camera: app-lifecycle pause / resume (Phase 6 polish)
Camera page kept the session running while the host app was
backgrounded — wastes battery, holds the hardware, and blocks
other apps from grabbing the camera. Add per-platform observers
that pause/resume the session on app foreground/background, with
a uniform `pauseForBackground` / `resumeForForeground` pair on the
shared CameraInstance.

Behaviour:
  - On background: any in-flight recording is hard-cancelled
    (matches every messaging app — the take ends with the app
    switch). The session stops so the OS can release the camera.
  - On foreground: session restarts iff it had been running.
    Emits `sessionInterrupted` (`reason: appBackgrounded`) and
    `sessionResumed` events so the Dart side can surface UX
    affordances if needed.

iOS — `ios/Classes/Camera/CameraInstance+iOS.swift`:
  Subscribes to UIApplication.{willResignActive, didBecomeActive}
  notifications. Work hops onto sessionQueue so AV mutations stay
  serialised. Storage uses the shared
  `CameraInstance.lifecycleCleanup` closure slot — extension
  doesn't need to add stored properties.

Android — added `androidx.lifecycle:lifecycle-process:2.7.0`,
  observes `ProcessLifecycleOwner.get().lifecycle`. ON_STOP →
  `pauseForBackground` (cancels recording + drops
  CustomLifecycleOwner to CREATED → CameraX releases camera).
  ON_START → `resumeForForeground`. Observer add/remove on main
  thread per `ProcessLifecycleOwner` contract.

macOS — `macos/Classes/Camera/CameraInstance+macOS.swift`:
  Intentional no-op. macOS desktop background semantics are
  softer; the chat composer's Card dialog typically stays
  foregrounded. Slot is wired so the shared
  `observeLifecycle()` call still compiles.

Verified: all four platforms (iOS / Android / macOS / app tests)
build clean. Pod install picks up the new iOS extension file
once Pods/ is fresh — `flutter clean` if mid-iteration.
2026-05-13 21:43:50 +03:00

42 lines
1.6 KiB
Swift

import Foundation
import UIKit
/// iOS-only app-lifecycle observer for [CameraInstance]. Hooks
/// `UIApplication.willResignActiveNotification` /
/// `.didBecomeActiveNotification` so backgrounding the app releases
/// the camera (and any in-flight recording is hard-cancelled
/// matching every messaging app), and foregrounding restarts the
/// session if it had been running. macOS counterpart is a no-op
/// (`CameraInstance+macOS.swift`) desktop background semantics are
/// less load-bearing for chat composer use.
///
/// `willResignActive` fires for phone calls and the app switcher;
/// `didBecomeActive` fires when the user returns. We hop work onto
/// the instance's `sessionQueue` so the AV teardown / start runs
/// serialised with other session mutations.
extension CameraInstance {
func observeLifecycle() {
let center = NotificationCenter.default
var observers: [NSObjectProtocol] = []
observers.append(center.addObserver(
forName: UIApplication.willResignActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
guard let self = self else { return }
self.sessionQueueAsync { self.pauseForBackground() }
})
observers.append(center.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
guard let self = self else { return }
self.sessionQueueAsync { self.resumeForForeground() }
})
lifecycleCleanup = {
for o in observers { center.removeObserver(o) }
}
}
}