Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions api/src/main/java/com/getcode/model/Feature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,8 @@ data class BalanceCurrencyFeature(
override val available: Boolean = true, // always available
): Feature

data class CameraAFFeature(
override val enabled: Boolean = BetaOptions.Defaults.cameraAFEnabled,
override val available: Boolean = true, // always available
): Feature

data class CameraZoomFeature(
override val enabled: Boolean = BetaOptions.Defaults.cameraPinchZoomEnabled,
data class CameraGesturesFeature(
override val enabled: Boolean = BetaOptions.Defaults.cameraGesturesEnabled,
override val available: Boolean = true, // always available
): Feature

Expand Down
3 changes: 1 addition & 2 deletions api/src/main/java/com/getcode/model/PrefBool.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ sealed class PrefsBool(val value: String) {
data object SHARE_TWEET_TO_TIP : PrefsBool("share_tweet_to_tip"), BetaFlag, Immutable
data object TIP_CARD_ON_HOMESCREEN: PrefsBool("tip_card_on_home_screen"), BetaFlag, Immutable
data object TIP_CARD_FLIPPABLE: PrefsBool("tipcard_flippable"), BetaFlag
data object CAMERA_AF_ENABLED: PrefsBool("camera_af_enabled"), BetaFlag
data object CAMERA_PINCH_ZOOM: PrefsBool("camera_pinch_zoom_enabled"), BetaFlag
data object CAMERA_GESTURES_ENABLED: PrefsBool("camera_gestures_enabled"), BetaFlag
}

val APP_SETTINGS: List<AppSetting> = listOf(PrefsBool.CAMERA_START_BY_DEFAULT, PrefsBool.REQUIRE_BIOMETRICS)
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ data class BetaOptions(
val kadoWebViewEnabled: Boolean,
val shareTweetToTip: Boolean,
val tipCardOnHomeScreen: Boolean,
val cameraAFEnabled: Boolean,
val cameraPinchZoomEnabled: Boolean,
val cameraGesturesEnabled: Boolean,
val canFlipTipCard: Boolean,
) {
companion object {
Expand All @@ -43,8 +42,7 @@ data class BetaOptions(
kadoWebViewEnabled = false,
shareTweetToTip = true,
tipCardOnHomeScreen = true,
cameraAFEnabled = true,
cameraPinchZoomEnabled = true,
cameraGesturesEnabled = true,
canFlipTipCard = false
)
}
Expand Down Expand Up @@ -83,8 +81,7 @@ class BetaFlagsRepository @Inject constructor(
observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled),
observeBetaFlag(PrefsBool.SHARE_TWEET_TO_TIP, default = defaults.shareTweetToTip),
observeBetaFlag(PrefsBool.TIP_CARD_ON_HOMESCREEN, defaults.tipCardOnHomeScreen),
observeBetaFlag(PrefsBool.CAMERA_AF_ENABLED, defaults.cameraAFEnabled),
observeBetaFlag(PrefsBool.CAMERA_PINCH_ZOOM, defaults.cameraPinchZoomEnabled),
observeBetaFlag(PrefsBool.CAMERA_GESTURES_ENABLED, defaults.cameraGesturesEnabled),
observeBetaFlag(PrefsBool.TIP_CARD_FLIPPABLE, defaults.canFlipTipCard)
) {
BetaOptions(
Expand All @@ -103,9 +100,8 @@ class BetaFlagsRepository @Inject constructor(
kadoWebViewEnabled = it[12],
shareTweetToTip = it[13],
tipCardOnHomeScreen = it[14],
cameraAFEnabled = it[15],
cameraPinchZoomEnabled = it[16],
canFlipTipCard = it[17]
cameraGesturesEnabled = it[15],
canFlipTipCard = it[16],
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.getcode.network.repository

import com.getcode.model.BalanceCurrencyFeature
import com.getcode.model.BuyModuleFeature
import com.getcode.model.CameraAFFeature
import com.getcode.model.CameraGesturesFeature
import com.getcode.model.PrefsBool
import com.getcode.model.RequestKinFeature
import com.getcode.model.TipCardFeature
Expand Down Expand Up @@ -32,8 +32,7 @@ class FeatureRepository @Inject constructor(
val conversations = betaFlags.observe().map { ConversationsFeature(it.conversationsEnabled) }
val conversationsCash = betaFlags.observe().map { ConversationCashFeature(it.conversationCashEnabled) }

val cameraAutoFocus = betaFlags.observe().map { CameraAFFeature(it.cameraAFEnabled) }
val cameraPinchZoom = betaFlags.observe().map { CameraAFFeature(it.cameraPinchZoomEnabled) }
val cameraGestures = betaFlags.observe().map { CameraGesturesFeature(it.cameraGesturesEnabled) }

val requestKin = betaFlags.observe().map { RequestKinFeature(it.giveRequestsEnabled) }

Expand Down
14 changes: 4 additions & 10 deletions app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,10 @@ fun BetaFlagsScreen(
state.shareTweetToTip,
),
BetaFeature(
PrefsBool.CAMERA_AF_ENABLED,
R.string.beta_camera_af,
stringResource(id = R.string.beta_camera_af_description),
state.cameraAFEnabled,
),
BetaFeature(
PrefsBool.CAMERA_PINCH_ZOOM,
R.string.beta_camera_pinch_zoom,
stringResource(id = R.string.beta_camera_pinch_zoom_description),
state.cameraPinchZoomEnabled,
PrefsBool.CAMERA_GESTURES_ENABLED,
R.string.beta_camera_gestures,
stringResource(id = R.string.beta_camera_gestures_description),
state.cameraGesturesEnabled,
),
BetaFeature(
PrefsBool.TIP_CARD_FLIPPABLE,
Expand Down
117 changes: 94 additions & 23 deletions app/src/main/java/com/getcode/view/main/camera/CodeScanner.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.getcode.view.main.camera

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
Expand Down Expand Up @@ -57,8 +59,7 @@ import java.util.concurrent.TimeUnit
@Composable
fun CodeScanner(
scanningEnabled: Boolean,
cameraAFEnabled: Boolean,
cameraPinchZoomEnabled: Boolean,
cameraGesturesEnabled: Boolean,
onPreviewStateChanged: (Boolean) -> Unit,
onCodeScanned: (ScannableKikCode) -> Unit
) {
Expand Down Expand Up @@ -92,15 +93,15 @@ fun CodeScanner(

val cameraExecutor = remember { Executors.newSingleThreadExecutor() }

var camera by remember { mutableStateOf<Camera?>(null) }
var autoFocusPoint by remember { mutableStateOf(Offset.Unspecified) }

val kikCodeAnalyzer = remember(scanner, onCodeScanned) {
KikCodeAnalyzer(scanner, onCodeScanned)
}

val biometricsState = LocalBiometricsState.current

var camera by remember { mutableStateOf<Camera?>(null) }
var autoFocusPoint by remember { mutableStateOf(Offset.Unspecified) }

val scope = rememberCoroutineScope()
LaunchedEffect(scanner, biometricsState.isAwaitingAuthentication, Biometrics.promptActive) {
val active = Biometrics.promptActive || biometricsState.isAwaitingAuthentication
Expand Down Expand Up @@ -143,7 +144,7 @@ fun CodeScanner(
}
}

LaunchedEffect(camera, cameraAFEnabled, cameraPinchZoomEnabled) {
LaunchedEffect(camera, cameraGesturesEnabled) {
camera?.let {
val cameraControl = it.cameraControl
val cameraInfo = it.cameraInfo
Expand All @@ -152,8 +153,7 @@ fun CodeScanner(
previewView,
cameraControl,
cameraInfo,
cameraAFEnabled,
cameraPinchZoomEnabled
cameraGesturesEnabled,
) { point ->
autoFocusPoint = point
}
Expand Down Expand Up @@ -219,13 +219,20 @@ private fun setupInteractionControls(
previewView: PreviewView,
cameraControl: CameraControl,
cameraInfo: CameraInfo,
autoFocusEnabled: Boolean,
pinchZoomEnabled: Boolean,
cameraGesturesEnabled: Boolean,
onTap: (Offset) -> Unit,
) {
var isPinchZooming = false

// Pinch-to-zoom gesture detector
val scaleGestureDetector = ScaleGestureDetector(
previewView.context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
isPinchZooming = true
return true
}

override fun onScale(detector: ScaleGestureDetector): Boolean {
val currentZoomRatio = cameraInfo.zoomState.value?.zoomRatio ?: 1f
val delta = detector.scaleFactor
Expand All @@ -241,38 +248,102 @@ private fun setupInteractionControls(
cameraControl.setZoomRatio(clampedZoomRatio)
return true
}

override fun onScaleEnd(detector: ScaleGestureDetector) {
isPinchZooming = false
}
})

// Create a gesture detector to detect tap gestures
val gestureDetector =
GestureDetector(previewView.context, object : GestureDetector.SimpleOnGestureListener() {
// Gesture detector for tap and drag-to-zoom
val gestureDetector = GestureDetector(
previewView.context,
object : GestureDetector.OnGestureListener {
private var initialZoomLevel = 0f
private var accumulatedDelta = 0f

override fun onDown(e: MotionEvent): Boolean {
initialZoomLevel = cameraInfo.zoomState.value?.zoomRatio ?: 1f
accumulatedDelta = 0f
return true
}

override fun onSingleTapUp(event: MotionEvent): Boolean {
// Get the tap location
val point = previewView.meteringPointFactory.createPoint(event.x, event.y)
onTap(Offset(event.x, event.y))

// Prepare focus action
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
.setAutoCancelDuration(
5,
TimeUnit.SECONDS
) // Optional: Auto-cancel after 5 seconds
.setAutoCancelDuration(5, TimeUnit.SECONDS)
.build()

// Trigger focus and metering at the tapped location
cameraControl.startFocusAndMetering(action)
return true
}

override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (!isPinchZooming) {
accumulatedDelta += distanceY

val deltaZoom = accumulatedDelta / 1000f

val maxZoom = cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
val minZoom = cameraInfo.zoomState.value?.minZoomRatio ?: 1f

val newZoom = (initialZoomLevel + deltaZoom).coerceIn(minZoom, maxZoom)
cameraControl.setZoomRatio(newZoom)
}
return true
}

override fun onShowPress(e: MotionEvent) {}
override fun onLongPress(e: MotionEvent) {}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
return false
}
})

previewView.setOnTouchListener { _, event ->
if (pinchZoomEnabled) {
if (cameraGesturesEnabled) {
scaleGestureDetector.onTouchEvent(event)
}
if (autoFocusEnabled) {
gestureDetector.onTouchEvent(event)

if (event.action == MotionEvent.ACTION_UP) {
animateZoomReset(cameraInfo, cameraControl)
}
}
true
}
}

private fun animateZoomReset(cameraInfo: CameraInfo, cameraControl: CameraControl) {
val handler = Handler(Looper.getMainLooper())
val durationMs = 300L
val frameInterval = 16L
val maxSteps = durationMs / frameInterval
val currentZoomLevel = cameraInfo.zoomState.value?.linearZoom ?: 0f

val decrement = currentZoomLevel / maxSteps

var currentStep = 0L
handler.post(object : Runnable {
override fun run() {
if (currentStep < maxSteps) {
val newZoomLevel = currentZoomLevel - (decrement * currentStep)
cameraControl.setLinearZoom(newZoomLevel.coerceIn(0f, 1f))
currentStep++
handler.postDelayed(this, frameInterval)
} else {
cameraControl.setLinearZoom(0f)
}
}
})
}
3 changes: 1 addition & 2 deletions app/src/main/java/com/getcode/view/main/home/HomeScan.kt
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,7 @@ private fun HomeScan(
scannerView = {
CodeScanner(
scanningEnabled = previewing,
cameraAFEnabled = dataState.cameraAutoFocus.enabled,
cameraPinchZoomEnabled = dataState.cameraPinchZoom.enabled,
cameraGesturesEnabled = dataState.cameraGestures.enabled,
onPreviewStateChanged = { previewing = it },
onCodeScanned = {
if (previewing) {
Expand Down
18 changes: 4 additions & 14 deletions app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import com.getcode.manager.ModalManager
import com.getcode.manager.SessionManager
import com.getcode.manager.TopBarManager
import com.getcode.model.BuyModuleFeature
import com.getcode.model.CameraAFFeature
import com.getcode.model.CameraZoomFeature
import com.getcode.model.CameraGesturesFeature
import com.getcode.model.CodePayload
import com.getcode.model.Currency
import com.getcode.model.Domain
Expand Down Expand Up @@ -160,8 +159,7 @@ data class HomeUiModel(
val notificationUnreadCount: Int = 0,
val buyModule: Feature = BuyModuleFeature(),
val requestKin: Feature = RequestKinFeature(),
val cameraAutoFocus: Feature = CameraAFFeature(),
val cameraPinchZoom: Feature = CameraZoomFeature(),
val cameraGestures: Feature = CameraGesturesFeature(),
val flippableTipCard: Feature = FlippableTipCardFeature(),
val actions: List<HomeAction> = listOf(HomeAction.GIVE_KIN, HomeAction.TIP_CARD, HomeAction.BALANCE),
val tipCardConnected: Boolean = false,
Expand Down Expand Up @@ -240,19 +238,11 @@ class HomeViewModel @Inject constructor(
}
}.launchIn(viewModelScope)

features.cameraAutoFocus
features.cameraGestures
.distinctUntilChanged()
.onEach { module ->
uiFlow.update {
it.copy(cameraAutoFocus = module)
}
}.launchIn(viewModelScope)

features.cameraPinchZoom
.distinctUntilChanged()
.onEach { module ->
uiFlow.update {
it.copy(cameraPinchZoom = module)
it.copy(cameraGestures = module)
}
}.launchIn(viewModelScope)

Expand Down
6 changes: 2 additions & 4 deletions app/src/main/res/values/strings-universal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
<string translatable="false" name="beta_balance_currency">Currency Selection in Balance</string>
<string translatable="false" name="beta_kado_webview">Buy Kin Internally</string>
<string translatable="false" name="beta_share_tweet_tip">Share Tweets to Tip</string>
<string translatable="false" name="beta_camera_af">Camera Auto Focus</string>
<string translatable="false" name="beta_camera_pinch_zoom">Camera Pinch to Zoom</string>
<string translatable="false" name="beta_camera_gestures">Camera Gestures</string>
<string translatable="false" name="beta_tipcard_can_flip">Tap Tip Card to See Back</string>
<string translatable="false" name="beta_display_errors">Show Errors</string>
<string name="beta_bucket_debugger_description" translatable="false">If enabled, you\'ll gain the ability to tap the balance on the Balance screen to inspect individual bucket balances.</string>
Expand All @@ -44,8 +43,7 @@
<string name="beta_conversations_cash_description" translatable="false">If enabled, you\'ll gain the ability to send cash in conversations.</string>
<string name="beta_kado_webview_description" translatable="false">If enabled, the Buy Kin flow will open in an internal WebView.</string>
<string name="beta_share_tweet_tip_description" translatable="false">If enabled, you\'ll gain the ability to share tweets directly from Twitter to Code to tip the author.</string>
<string name="beta_camera_af_description" translatable="false">If enabled, you\'ll gain the ability to manually trigger auto focus by tapping the camera.</string>
<string name="beta_camera_pinch_zoom_description" translatable="false">If enabled, you\'ll gain the ability to pinch to zoom in on the camera.</string>
<string name="beta_camera_gestures_description" translatable="false">If enabled, you\'ll gain the ability to pinch-to-zoom, drag-to-zoom, and tap to auto focus on the camera screen.</string>
<string name="beta_tipcard_can_flip_description" translatable="false">If enabled, you\'ll gain the ability to tap your own Tip Card to see the back.</string>
<string name="subtitle_remoteSendText" translatable="false">%1$s %2$s</string>
<string name="beta_resetTooltips" translatable="false">Reset Tooltips</string>
Expand Down