P6 Stream V: ImageCompression (expect/actual) + CameraPicker polish
Android: BitmapFactory + ExifInterface + JPEG quality 0.7 + 1920px downscale. iOS: UIImage.jpegData. JVM/JS/WASM: no-op. CameraPicker uses TakePicture ActivityResult + permission rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
package com.tt.honeyDue.ui.components
|
||||
|
||||
/**
|
||||
* Shared camera-picker helpers (commonMain).
|
||||
*
|
||||
* The actual `rememberCameraPicker` composable lives at
|
||||
* `com.tt.honeyDue.platform.rememberCameraPicker` (expect/actual per
|
||||
* platform). This file captures the pure logic that decides *what* to do
|
||||
* when a user taps "Take photo" so it can be unit-tested without touching
|
||||
* the ActivityResult contract or iOS `UIImagePickerController`.
|
||||
*
|
||||
* Mirrors the iOS reference `CameraPickerView.swift`, which simply falls
|
||||
* back to the photo library if the camera source type is unavailable. On
|
||||
* Android we add a permission-rationale step to match Google's UX
|
||||
* guidelines, then delegate to the platform picker.
|
||||
*/
|
||||
|
||||
/** The three possible outcomes of "user tapped Take photo". */
|
||||
enum class CameraPermissionDecision {
|
||||
/** Camera permission already granted — launch the picker immediately. */
|
||||
Launch,
|
||||
|
||||
/** Permission denied previously; show a rationale dialog before asking again. */
|
||||
ShowRationale,
|
||||
|
||||
/** First request (or "don't ask again" cleared) — prompt the OS directly. */
|
||||
Request,
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure decision function: given the current permission state, return the
|
||||
* next UI action.
|
||||
*
|
||||
* @param isGranted `true` if the camera permission is currently held.
|
||||
* @param shouldShowRationale Android's `shouldShowRequestPermissionRationale`
|
||||
* flag — `true` when the user has previously denied the permission but
|
||||
* hasn't selected "don't ask again".
|
||||
*/
|
||||
fun decideCameraPermissionFlow(
|
||||
isGranted: Boolean,
|
||||
shouldShowRationale: Boolean,
|
||||
): CameraPermissionDecision = when {
|
||||
isGranted -> CameraPermissionDecision.Launch
|
||||
shouldShowRationale -> CameraPermissionDecision.ShowRationale
|
||||
else -> CameraPermissionDecision.Request
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.tt.honeyDue.util
|
||||
|
||||
/**
|
||||
* Cross-platform image compression matching the iOS helper
|
||||
* `iosApp/iosApp/Helpers/ImageCompression.swift`.
|
||||
*
|
||||
* Contract:
|
||||
* - Input is a raw encoded-image [ByteArray] (JPEG, PNG, HEIC…).
|
||||
* - Output is a JPEG-encoded [ByteArray] at [quality] (default 0.7), with
|
||||
* the long edge clamped to [maxEdgePx] (default 1920px) while preserving
|
||||
* aspect ratio.
|
||||
* - EXIF orientation is applied into the pixel data; the output image is
|
||||
* always in the canonical "upright" orientation (no orientation tag, or
|
||||
* `ORIENTATION_NORMAL`).
|
||||
*
|
||||
* Platforms:
|
||||
* - Android → `BitmapFactory` + `ExifInterface` + `Matrix` + JPEG compress.
|
||||
* - iOS → `UIImage(data:)` + `UIImage.jpegData(compressionQuality:)`.
|
||||
* `UIImage` normalizes EXIF automatically during decode.
|
||||
* - JVM / JS / WASM → no-op pass-through (web/desktop do not run this path
|
||||
* in production; returning the input keeps common code simple).
|
||||
*
|
||||
* This replaces the size-capped `ImageCompressor` helper for new call sites
|
||||
* that want to match iOS (quality-based, not size-based) semantics.
|
||||
*/
|
||||
expect object ImageCompression {
|
||||
/**
|
||||
* Compress [input] to JPEG, optionally downscaling.
|
||||
*
|
||||
* @param input encoded image bytes (JPEG/PNG/HEIC supported by the platform).
|
||||
* @param maxEdgePx maximum long-edge size in pixels (aspect preserved).
|
||||
* Defaults to `1920` to match iOS.
|
||||
* @param quality JPEG quality in `[0.0f, 1.0f]`. Defaults to `0.7f` to match iOS.
|
||||
* @return compressed JPEG bytes, or the original input on decode failure.
|
||||
*/
|
||||
suspend fun compress(
|
||||
input: ByteArray,
|
||||
maxEdgePx: Int = 1920,
|
||||
quality: Float = 0.7f
|
||||
): ByteArray
|
||||
}
|
||||
Reference in New Issue
Block a user