agra 5cd3505272 camera: Android photo + preview + lifecycle (Phase 4a)
CameraX-backed Android implementation matching the iOS plugin's
surface (ux/camera + ux/camera/events), photo-capable only. Video
recording lands in Phase 4b (VideoCapture<Recorder>).

Modules in android/src/main/kotlin/io/swipelab/ux/camera/:
  CustomLifecycleOwner     drives ProcessCameraProvider's bindings
                           STARTED ↔ DESTROYED per instance
  DeviceOrientationBridge  OrientationEventListener → Surface.ROTATION_*
                           with 22.5° hysteresis; flutterToSurfaceRotation
                           + surfaceRotationToFlutter encode/decode the
                           four-quadrant wire enum the iOS plugin uses
  PreviewSink              CameraX Preview.SurfaceProvider →
                           SurfaceTexture → FlutterTexture (stable
                           textureId across resolution renegotiations)
  PhotoCapture             ImageCapture wrapper, per-shot
                           setTargetRotation, JPEG to cache dir
  CameraInstance           per-controller state: lifecycle owner,
                           texture, ProcessCameraProvider binding,
                           photo + preview use-cases, lens swap
  CameraPlugin             channel + permission flow: camera always,
                           mic optional (matches iOS' "camera denial
                           is the only hard failure" model)

UxPlugin.kt registers CameraPlugin alongside the other plugins.

Channel parity with iOS:
  availableCameras, create, initialize, dispose, setDescription,
  setFlashMode, lockCaptureOrientation/unlock (no-op; preview is
  pinned portrait), takePicture, audioPermissionStatus, openSettings.

startVideoRecording / stopVideoRecording return `unsupported_format`
until Phase 4b. Camera-device contention via lensesInUse + audio
claim via audioInUse mirror iOS's tracking, including the
setDescription swap (remove old lens / insert new) that closed the
device_busy leak on iOS.

Android APK builds clean against compileSdk 34, CameraX 1.3.4.
2026-05-13 17:25:28 +03:00
...
2026-05-10 16:37:23 +03:00
2026-04-16 18:42:55 +03:00
2019-08-07 15:37:44 +01:00
2026-04-21 16:54:56 +03:00
2026-05-07 09:22:01 +03:00
2019-10-17 14:34:21 +01:00
2026-05-07 09:22:01 +03:00
2026-04-16 18:42:55 +03:00
zzz
2020-05-04 10:58:19 +01:00

ux

A Flutter toolkit for building fluid, native-feeling UIs.

UxKeyboard

Frame-accurate keyboard height tracking for iOS and Android, with interactive dismiss.

Flutter's built-in MediaQuery.viewInsets.bottom lags behind the actual keyboard position and doesn't support interactive dismiss. UxKeyboard reads the keyboard height directly from the native layer via FFI — zero channel latency, every frame.

Features

  • Real-time height — reads the keyboard's actual position each frame via FFI (iOS) / JNI (Android)
  • Native animation curves — sampled from CADisplayLink (iOS) and WindowInsetsAnimation (Android), with adaptive learning that refines the curve from observations
  • Interactive dismiss — swipe the keyboard down like iMessage/Telegram, with snap-back or dismiss
  • Scroll freezeisTracking flag lets you freeze scrolling during interactive dismiss

Quick start

final keyboard = UxKeyboard.instance;

// Enable swipe-to-dismiss. trackingInset is the height of your input bar.
keyboard.enableInteractiveDismiss(trackingInset: 56);

Use ListenableBuilder to rebuild when the keyboard height changes:

Scaffold(
  resizeToAvoidBottomInset: false, // we handle it ourselves
  body: ListenableBuilder(
    listenable: keyboard,
    builder: (context, _) {
      final keyboardHeight = keyboard.height;
      final safeBottom = MediaQuery.viewPaddingOf(context).bottom;
      final bottom = max(keyboardHeight, safeBottom);

      return Column(
        children: [
          Expanded(
            child: ListView.builder(
              reverse: true,
              // Freeze scrolling during interactive dismiss
              physics: keyboard.isTracking
                  ? NeverScrollableScrollPhysics()
                  : null,
              // ...
            ),
          ),
          Container(
            padding: EdgeInsets.only(bottom: 8 + bottom),
            // your input bar
          ),
        ],
      );
    },
  ),
);

API

Member Description
UxKeyboard.instance Singleton instance
.height Current keyboard height in logical pixels
.systemHeight Last system-reported keyboard height
.isOpen Whether the keyboard is visible
.isTracking Whether a dismiss pan gesture is active
.enableInteractiveDismiss({trackingInset}) Enable swipe-to-dismiss
.disableInteractiveDismiss() Disable swipe-to-dismiss
addListener / removeListener Standard ChangeNotifier API

Key points

  • Set resizeToAvoidBottomInset: false on your Scaffold — otherwise Flutter's built-in resize fights with UxKeyboard
  • Use MediaQuery.viewPaddingOf(context).bottom for the safe area (not paddingOf, which is consumed by Scaffold)
  • Use max(keyboardHeight, safeBottom) for bottom padding — the keyboard height includes the safe area when open, and safeBottom covers the home indicator when closed

Other utilities

  • BendBox — a flexible layout widget
  • Bezier — bezier curve utilities
Description
No description provided
Readme MIT 1.5 MiB
Languages
Dart 40.2%
Swift 29.1%
Kotlin 18.4%
Java 5.2%
C++ 3.1%
Other 4%