Skip to content

Commit ded4948

Browse files
authored
Merge pull request #125 from jadlr/feature/preview-scale-type
Feature/camera scale type
2 parents c9b78d2 + e7d9ba4 commit ded4948

5 files changed

Lines changed: 69 additions & 15 deletions

File tree

library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CameraCapturePreview.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import androidx.camera.view.PreviewView
77
import androidx.compose.foundation.background
88
import androidx.compose.foundation.clickable
99
import androidx.compose.foundation.interaction.MutableInteractionSource
10-
import androidx.compose.foundation.isSystemInDarkTheme
1110
import androidx.compose.foundation.layout.Box
1211
import androidx.compose.foundation.layout.fillMaxSize
1312
import androidx.compose.foundation.layout.padding
1413
import androidx.compose.foundation.layout.size
1514
import androidx.compose.foundation.shape.CircleShape
1615
import androidx.compose.material.Icon
17-
import androidx.compose.material.MaterialTheme
1816
import androidx.compose.material.icons.Icons
1917
import androidx.compose.material.icons.filled.Cached
2018
import androidx.compose.runtime.Composable
@@ -32,11 +30,11 @@ import androidx.compose.ui.unit.dp
3230
import androidx.compose.ui.viewinterop.AndroidView
3331
import io.github.ismoy.imagepickerkmp.data.models.FlashMode
3432
import io.github.ismoy.imagepickerkmp.domain.config.CameraPreviewConfig
35-
import io.github.ismoy.imagepickerkmp.domain.config.ImagePickerUiConstants.BackgroundColor
3633
import io.github.ismoy.imagepickerkmp.domain.config.ImagePickerUiConstants.DELAY_TO_TAKE_PHOTO
3734
import io.github.ismoy.imagepickerkmp.domain.models.CapturePhotoPreference
3835
import io.github.ismoy.imagepickerkmp.domain.models.CompressionLevel
3936
import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult
37+
import io.github.ismoy.imagepickerkmp.domain.models.CameraScaleType
4038
import io.github.ismoy.imagepickerkmp.presentation.ui.extensions.activity
4139
import io.github.ismoy.imagepickerkmp.presentation.ui.utils.playShutterSound
4240
import io.github.ismoy.imagepickerkmp.presentation.ui.utils.rememberCameraManager
@@ -84,8 +82,6 @@ fun CameraCapturePreview(
8482
playShutterSound()
8583
stateHolder?.capturePhoto(onPhotoResult, onError, compressionLevel, includeExif, redactGpsData)
8684
}
87-
val isDark = isSystemInDarkTheme()
88-
val backgroundColor = if (isDark) MaterialTheme.colors.background else BackgroundColor
8985
val resolvedButtonColor = previewConfig.uiConfig.buttonColor ?: Color.Gray
9086
val resolvedIconColor = previewConfig.uiConfig.iconColor ?: Color.White
9187
val resolvedButtonSize = previewConfig.uiConfig.buttonSize ?: 56.dp
@@ -102,12 +98,12 @@ fun CameraCapturePreview(
10298
}
10399
}
104100

105-
Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
101+
Box(modifier = Modifier.fillMaxSize().background(Color.Black)) {
106102
AndroidView(
107103
factory = { context ->
108104
PreviewView(context).apply {
109-
scaleType = PreviewView.ScaleType.FILL_CENTER
110-
105+
scaleType = previewConfig.cameraScaleType.toPreviewViewScaleType()
106+
111107
implementationMode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
112108
PreviewView.ImplementationMode.COMPATIBLE
113109
} else {
@@ -183,3 +179,12 @@ fun CameraCapturePreview(
183179
FlashOverlay(visible = stateHolder?.showFlashOverlay ?: false)
184180
}
185181
}
182+
183+
private fun CameraScaleType.toPreviewViewScaleType(): PreviewView.ScaleType = when (this) {
184+
CameraScaleType.FILL_CENTER -> PreviewView.ScaleType.FILL_CENTER
185+
CameraScaleType.FILL_START -> PreviewView.ScaleType.FILL_START
186+
CameraScaleType.FILL_END -> PreviewView.ScaleType.FILL_END
187+
CameraScaleType.FIT_CENTER -> PreviewView.ScaleType.FIT_CENTER
188+
CameraScaleType.FIT_START -> PreviewView.ScaleType.FIT_START
189+
CameraScaleType.FIT_END -> PreviewView.ScaleType.FIT_END
190+
}

library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/screens/CameraCaptureView.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ private fun CameraAndPreview(
173173
previewConfig = CameraPreviewConfig(
174174
captureButtonSize = cameraCaptureConfig.captureButtonSize,
175175
uiConfig = cameraCaptureConfig.uiConfig,
176-
cameraCallbacks = cameraCaptureConfig.cameraCallbacks
176+
cameraCallbacks = cameraCaptureConfig.cameraCallbacks,
177+
cameraScaleType = cameraCaptureConfig.cameraScaleType,
177178
),
178179
compressionLevel = cameraCaptureConfig.compressionLevel,
179180
includeExif = cameraCaptureConfig.includeExif,

library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/config/ImagePickerConfig.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.github.ismoy.imagepickerkmp.domain.models.CapturePhotoPreference
99
import io.github.ismoy.imagepickerkmp.domain.models.CompressionLevel
1010
import io.github.ismoy.imagepickerkmp.domain.models.MimeType
1111
import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult
12+
import io.github.ismoy.imagepickerkmp.domain.models.CameraScaleType
1213

1314
/**
1415
* Configuration for the visual styling of camera UI elements.
@@ -161,6 +162,11 @@ data class CropConfig(
161162
* post-capture confirmation screen. See [PermissionAndConfirmationConfig].
162163
* @property cropConfig Configuration for the interactive crop UI shown after capture.
163164
* See [CropConfig].
165+
* @property cameraScaleType How the camera preview is scaled inside its viewport.
166+
* Defaults to [CameraScaleType.FILL_CENTER] (preserves prior behavior — fills the
167+
* viewport, cropping the camera feed to fit). Use [CameraScaleType.FIT_CENTER] to
168+
* letterbox the preview so the viewfinder framing matches the captured image.
169+
* Currently applied on Android only.
164170
*/
165171
@Suppress("EndOfSentenceFormat")
166172
data class CameraCaptureConfig(
@@ -172,7 +178,8 @@ data class CameraCaptureConfig(
172178
val uiConfig: UiConfig = UiConfig(),
173179
val cameraCallbacks: CameraCallbacks = CameraCallbacks(),
174180
val permissionAndConfirmationConfig: PermissionAndConfirmationConfig = PermissionAndConfirmationConfig(),
175-
val cropConfig: CropConfig = CropConfig()
181+
val cropConfig: CropConfig = CropConfig(),
182+
val cameraScaleType: CameraScaleType = CameraScaleType.FILL_CENTER,
176183
)
177184

178185
/**
@@ -271,7 +278,8 @@ data class ImagePickerConfig(
271278
data class CameraPreviewConfig(
272279
val captureButtonSize: Dp = 72.dp,
273280
val uiConfig: UiConfig = UiConfig(),
274-
val cameraCallbacks: CameraCallbacks = CameraCallbacks()
281+
val cameraCallbacks: CameraCallbacks = CameraCallbacks(),
282+
val cameraScaleType: CameraScaleType = CameraScaleType.FILL_CENTER,
275283
)
276284

277285
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.ismoy.imagepickerkmp.domain.models
2+
3+
/**
4+
* Controls how the camera preview is scaled inside its viewport.
5+
*
6+
* The preview surface and the captured image share an aspect ratio (4:3), but the
7+
* viewport that hosts the preview typically does not. The scale type decides how
8+
* the preview is fitted into that mismatched viewport.
9+
*
10+
* - **FILL_** values fill the viewport entirely and crop whatever does not fit.
11+
* The visible viewfinder will be narrower (or wider) than the captured image.
12+
* - **FIT_** values letterbox the preview so the entire camera feed is visible.
13+
* The viewfinder framing matches the captured image exactly.
14+
*
15+
* Only [FILL_CENTER] and [FIT_CENTER] are commonly useful. The remaining values
16+
* mirror [androidx.camera.view.PreviewView.ScaleType] for completeness.
17+
*
18+
* Currently applied on Android only. iOS uses the system camera UI, where preview
19+
* and capture framing already match.
20+
*/
21+
enum class CameraScaleType {
22+
FILL_CENTER,
23+
FILL_START,
24+
FILL_END,
25+
FIT_CENTER,
26+
FIT_START,
27+
FIT_END,
28+
}

library/src/commonTest/kotlin/io/github/ismoy/imagepickerkmp/domain/config/ImagePickerConfigTest.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.github.ismoy.imagepickerkmp.domain.config
33
import io.github.ismoy.imagepickerkmp.domain.models.CapturePhotoPreference
44
import io.github.ismoy.imagepickerkmp.domain.models.MimeType
55
import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult
6+
import io.github.ismoy.imagepickerkmp.domain.models.CameraScaleType
67
import kotlin.test.Test
78
import kotlin.test.assertEquals
89
import kotlin.test.assertFalse
@@ -37,17 +38,20 @@ class ImagePickerConfigTest {
3738
@Test
3839
fun `CameraCaptureConfig should have default values`() {
3940
val config = CameraCaptureConfig()
40-
41+
4142
assertEquals(CapturePhotoPreference.BALANCED, config.preference)
43+
assertEquals(CameraScaleType.FILL_CENTER, config.cameraScaleType)
4244
}
4345

4446
@Test
4547
fun `CameraCaptureConfig should allow custom values`() {
4648
val config = CameraCaptureConfig(
47-
preference = CapturePhotoPreference.FAST
49+
preference = CapturePhotoPreference.FAST,
50+
cameraScaleType = CameraScaleType.FIT_CENTER,
4851
)
49-
52+
5053
assertEquals(CapturePhotoPreference.FAST, config.preference)
54+
assertEquals(CameraScaleType.FIT_CENTER, config.cameraScaleType)
5155
}
5256

5357
@Test
@@ -86,10 +90,18 @@ class ImagePickerConfigTest {
8690
@Test
8791
fun `CameraPreviewConfig should have default values`() {
8892
val config = CameraPreviewConfig()
89-
93+
9094
assertFalse(config.captureButtonSize.value == 0f)
9195
assertEquals(null, config.uiConfig.buttonColor)
9296
assertEquals(null, config.cameraCallbacks.onCameraReady)
97+
assertEquals(CameraScaleType.FILL_CENTER, config.cameraScaleType)
98+
}
99+
100+
@Test
101+
fun `CameraPreviewConfig should allow custom cameraScaleType`() {
102+
val config = CameraPreviewConfig(cameraScaleType = CameraScaleType.FIT_CENTER)
103+
104+
assertEquals(CameraScaleType.FIT_CENTER, config.cameraScaleType)
93105
}
94106

95107
@Test

0 commit comments

Comments
 (0)