Skip to content

Commit 89ae8a1

Browse files
committed
add offset for lock screen overlay
1 parent 7ddcdf3 commit 89ae8a1

7 files changed

Lines changed: 226 additions & 28 deletions

File tree

app/src/androidTest/java/com/ah/taplock/SettingsUiTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,26 @@ class SettingsUiTest {
284284
)
285285
}
286286

287+
@Test
288+
fun updateLockZoneTopOffset_updatesPref() {
289+
getPrefs().edit()
290+
.putBoolean(context.getString(R.string.lock_screen_double_tap), true)
291+
.commit()
292+
293+
setScreenContent()
294+
295+
composeTestRule.onNodeWithTag("slider_lock_zone_top_offset")
296+
.performScrollTo()
297+
.performSemanticsAction(SemanticsActions.SetProgress) { setProgress ->
298+
assertTrue(setProgress(12f))
299+
}
300+
301+
assertEquals(
302+
12,
303+
getPrefs().getInt(context.getString(R.string.lock_zone_top_offset_percent), -1)
304+
)
305+
}
306+
287307
@Test
288308
fun highlightActiveArea_showsOverlayPreview() {
289309
getPrefs().edit()

app/src/main/java/com/ah/taplock/LockScreenZoneComponents.kt

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,22 @@ import androidx.compose.ui.graphics.Color
1818
import androidx.compose.ui.platform.testTag
1919
import androidx.compose.ui.unit.dp
2020

21-
private fun lockZoneFraction(lockZonePercent: Int): Float = (lockZonePercent / 100f).coerceIn(0.2f, 1f)
22-
2321
@Composable
2422
fun LockScreenZonePreview(
25-
lockZonePercent: Int
23+
lockZonePercent: Int,
24+
lockZoneTopOffsetPercent: Int
2625
) {
26+
val clampedLockZonePercent = TapLockLockZone.clampPercent(lockZonePercent)
27+
val clampedTopOffsetPercent = TapLockLockZone.clampTopOffsetPercent(
28+
lockZoneTopOffsetPercent,
29+
clampedLockZonePercent
30+
)
2731
val previewWidth = 120.dp
2832
val previewHeight = 220.dp
2933
val previewVerticalPadding = 16.dp
3034
val previewInnerHeight = 188.dp
31-
val highlightedHeight = previewInnerHeight * lockZoneFraction(lockZonePercent)
35+
val highlightedHeight = previewInnerHeight * (clampedLockZonePercent / 100f)
36+
val topInset = previewInnerHeight * (clampedTopOffsetPercent / 100f)
3237
val frameColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.6f)
3338
val zoneColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.28f)
3439
val shape = RoundedCornerShape(24.dp)
@@ -57,6 +62,7 @@ fun LockScreenZonePreview(
5762
Box(
5863
modifier = Modifier
5964
.align(Alignment.TopCenter)
65+
.padding(top = topInset)
6066
.fillMaxWidth()
6167
.height(highlightedHeight)
6268
.background(
@@ -71,14 +77,21 @@ fun LockScreenZonePreview(
7177

7278
@Composable
7379
fun LockScreenZoneLiveOverlay(
74-
lockZonePercent: Int
80+
lockZonePercent: Int,
81+
lockZoneTopOffsetPercent: Int
7582
) {
7683
BoxWithConstraints(
7784
modifier = Modifier
7885
.fillMaxSize()
7986
.testTag("lock_zone_live_overlay")
8087
) {
81-
val highlightedHeight = maxHeight * lockZoneFraction(lockZonePercent)
88+
val clampedLockZonePercent = TapLockLockZone.clampPercent(lockZonePercent)
89+
val clampedTopOffsetPercent = TapLockLockZone.clampTopOffsetPercent(
90+
lockZoneTopOffsetPercent,
91+
clampedLockZonePercent
92+
)
93+
val highlightedHeight = maxHeight * (clampedLockZonePercent / 100f)
94+
val topInset = maxHeight * (clampedTopOffsetPercent / 100f)
8295
val zoneColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.18f)
8396
val zoneBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
8497

@@ -91,6 +104,7 @@ fun LockScreenZoneLiveOverlay(
91104
Box(
92105
modifier = Modifier
93106
.align(Alignment.TopCenter)
107+
.padding(top = topInset)
94108
.fillMaxWidth()
95109
.height(highlightedHeight)
96110
.background(

app/src/main/java/com/ah/taplock/MainActivity.kt

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ fun TapLockScreen() {
153153
val floatingButtonKey = stringResource(R.string.floating_button_enabled)
154154
val floatingButtonSizeDpKey = stringResource(R.string.floating_button_size_dp)
155155
val floatingButtonOpacityPercentKey = stringResource(R.string.floating_button_opacity_percent)
156+
val lockZoneTopOffsetPercentKey = stringResource(R.string.lock_zone_top_offset_percent)
156157

157158
var showDialog by remember { mutableStateOf(false) }
158159

@@ -183,7 +184,10 @@ fun TapLockScreen() {
183184
var onboardingStep by remember { mutableIntStateOf(0) }
184185
var lockDelayMs by remember { mutableIntStateOf(0) }
185186
var lockCount by remember { mutableIntStateOf(0) }
186-
var lockZonePercent by remember { mutableFloatStateOf(66f) }
187+
var lockZonePercent by remember { mutableFloatStateOf(TapLockLockZone.DEFAULT_PERCENT.toFloat()) }
188+
var lockZoneTopOffsetPercent by remember {
189+
mutableFloatStateOf(TapLockLockZone.DEFAULT_TOP_OFFSET_PERCENT.toFloat())
190+
}
187191
var edgeZoneWidthDp by remember {
188192
mutableFloatStateOf(TapLockEdgeZones.DEFAULT_WIDTH_DP.toFloat())
189193
}
@@ -216,11 +220,13 @@ fun TapLockScreen() {
216220
val edgeBottomOffsetSliderInteraction = remember { MutableInteractionSource() }
217221
val cornerSizeSliderInteraction = remember { MutableInteractionSource() }
218222
val lockZoneSliderInteraction = remember { MutableInteractionSource() }
223+
val lockZoneTopOffsetSliderInteraction = remember { MutableInteractionSource() }
219224
val isEdgeWidthSliderDragged by edgeWidthSliderInteraction.collectIsDraggedAsState()
220225
val isEdgeTopOffsetSliderDragged by edgeTopOffsetSliderInteraction.collectIsDraggedAsState()
221226
val isEdgeBottomOffsetSliderDragged by edgeBottomOffsetSliderInteraction.collectIsDraggedAsState()
222227
val isCornerSizeSliderDragged by cornerSizeSliderInteraction.collectIsDraggedAsState()
223228
val isLockZoneSliderDragged by lockZoneSliderInteraction.collectIsDraggedAsState()
229+
val isLockZoneTopOffsetSliderDragged by lockZoneTopOffsetSliderInteraction.collectIsDraggedAsState()
224230
var showLockZonePreviewOverlay by remember { mutableStateOf(false) }
225231
val editableLeftEdgeZoneEnabled = leftEdgeZoneEnabled
226232
val editableRightEdgeZoneEnabled = rightEdgeZoneEnabled
@@ -246,7 +252,7 @@ fun TapLockScreen() {
246252
isCornerSizeSliderDragged
247253
)
248254
val showLockZoneLiveOverlay = lockScreenDoubleTap &&
249-
(isLockZoneSliderDragged || showLockZonePreviewOverlay)
255+
(isLockZoneSliderDragged || isLockZoneTopOffsetSliderDragged || showLockZonePreviewOverlay)
250256

251257
LaunchedEffect(showLockZonePreviewOverlay) {
252258
if (showLockZonePreviewOverlay) {
@@ -265,6 +271,27 @@ fun TapLockScreen() {
265271
.edit { putInt(baseKey, value) }
266272
}
267273

274+
fun syncLockZoneState(
275+
zonePercent: Int = lockZonePercent.toInt(),
276+
topOffsetPercent: Int = lockZoneTopOffsetPercent.toInt()
277+
) {
278+
val clampedZonePercent = TapLockLockZone.clampPercent(zonePercent)
279+
val clampedTopOffsetPercent = TapLockLockZone.clampTopOffsetPercent(
280+
topOffsetPercent,
281+
clampedZonePercent
282+
)
283+
lockZonePercent = clampedZonePercent.toFloat()
284+
lockZoneTopOffsetPercent = clampedTopOffsetPercent.toFloat()
285+
}
286+
287+
fun persistLockZoneSettings() {
288+
syncLockZoneState()
289+
context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE).edit {
290+
putInt(lockZonePercentKey, lockZonePercent.toInt())
291+
putInt(lockZoneTopOffsetPercentKey, lockZoneTopOffsetPercent.toInt())
292+
}
293+
}
294+
268295
fun setEditableEdgeEnabled(side: EdgeZoneSide, isEnabled: Boolean) {
269296
when (side) {
270297
EdgeZoneSide.LEFT -> leftEdgeZoneEnabled = isEnabled
@@ -366,7 +393,13 @@ fun TapLockScreen() {
366393
showOnboarding = !prefs.getBoolean(hasCompletedOnboardingKey, false)
367394
lockDelayMs = prefs.getInt(lockDelayMsKey, 0)
368395
lockCount = prefs.getInt(lockCountKey, 0)
369-
lockZonePercent = prefs.getInt(lockZonePercentKey, 66).toFloat()
396+
syncLockZoneState(
397+
zonePercent = prefs.getInt(lockZonePercentKey, TapLockLockZone.DEFAULT_PERCENT),
398+
topOffsetPercent = prefs.getInt(
399+
lockZoneTopOffsetPercentKey,
400+
TapLockLockZone.DEFAULT_TOP_OFFSET_PERCENT
401+
)
402+
)
370403
edgeZoneWidthDp = prefs.getInt(
371404
edgeZoneWidthDpKey,
372405
TapLockEdgeZones.DEFAULT_WIDTH_DP
@@ -1100,29 +1133,49 @@ fun TapLockScreen() {
11001133
}
11011134

11021135
if (lockScreenDoubleTap) {
1103-
val lockZoneKey = stringResource(R.string.lock_zone_percent)
1136+
val maxLockZoneTopOffsetPercent =
1137+
TapLockLockZone.maxTopOffsetPercent(lockZonePercent.toInt())
11041138
Text(
11051139
stringResource(R.string.lock_zone_label, lockZonePercent.toInt()),
11061140
style = MaterialTheme.typography.bodySmall
11071141
)
11081142
Slider(
11091143
value = lockZonePercent,
1110-
onValueChange = { lockZonePercent = it },
1111-
onValueChangeFinished = {
1112-
context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE)
1113-
.edit { putInt(lockZoneKey, lockZonePercent.toInt()) }
1114-
},
1115-
valueRange = 20f..100f,
1144+
onValueChange = { syncLockZoneState(zonePercent = it.toInt()) },
1145+
onValueChangeFinished = { persistLockZoneSettings() },
1146+
valueRange = TapLockLockZone.MIN_PERCENT.toFloat()..
1147+
TapLockLockZone.MAX_PERCENT.toFloat(),
11161148
steps = 15,
11171149
interactionSource = lockZoneSliderInteraction,
11181150
modifier = Modifier.testTag("slider_lock_zone")
11191151
)
1152+
Text(
1153+
stringResource(
1154+
R.string.lock_zone_top_offset_label,
1155+
lockZoneTopOffsetPercent.toInt()
1156+
),
1157+
style = MaterialTheme.typography.bodySmall
1158+
)
1159+
if (maxLockZoneTopOffsetPercent > 0) {
1160+
Slider(
1161+
value = lockZoneTopOffsetPercent,
1162+
onValueChange = { syncLockZoneState(topOffsetPercent = it.toInt()) },
1163+
onValueChangeFinished = { persistLockZoneSettings() },
1164+
valueRange = TapLockLockZone.MIN_TOP_OFFSET_PERCENT.toFloat()..
1165+
maxLockZoneTopOffsetPercent.toFloat(),
1166+
interactionSource = lockZoneTopOffsetSliderInteraction,
1167+
modifier = Modifier.testTag("slider_lock_zone_top_offset")
1168+
)
1169+
}
11201170
Text(
11211171
stringResource(R.string.lock_zone_preview_label),
11221172
style = MaterialTheme.typography.bodySmall,
11231173
color = MaterialTheme.colorScheme.onSurfaceVariant
11241174
)
1125-
LockScreenZonePreview(lockZonePercent = lockZonePercent.toInt())
1175+
LockScreenZonePreview(
1176+
lockZonePercent = lockZonePercent.toInt(),
1177+
lockZoneTopOffsetPercent = lockZoneTopOffsetPercent.toInt()
1178+
)
11261179
Button(
11271180
onClick = { showLockZonePreviewOverlay = true },
11281181
modifier = Modifier.testTag("button_lock_zone_preview")
@@ -1571,7 +1624,10 @@ fun TapLockScreen() {
15711624
}
15721625

15731626
if (showLockZoneLiveOverlay) {
1574-
LockScreenZoneLiveOverlay(lockZonePercent = lockZonePercent.toInt())
1627+
LockScreenZoneLiveOverlay(
1628+
lockZonePercent = lockZonePercent.toInt(),
1629+
lockZoneTopOffsetPercent = lockZoneTopOffsetPercent.toInt()
1630+
)
15751631
}
15761632

15771633
if (showEdgeZoneLiveOverlay) {

app/src/main/java/com/ah/taplock/TapLockAccessibilityService.kt

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ class TapLockAccessibilityService : AccessibilityService() {
151151
key == getString(R.string.lock_zone_percent) -> {
152152
if (statusBarOverlay != null) updateOverlayForLockScreen()
153153
}
154+
155+
key == getString(R.string.lock_zone_top_offset_percent) -> {
156+
if (statusBarOverlay != null) updateOverlayForLockScreen()
157+
}
154158
}
155159
}
156160
prefs.registerOnSharedPreferenceChangeListener(prefListener)
@@ -640,20 +644,22 @@ class TapLockAccessibilityService : AccessibilityService() {
640644

641645
val wm = getSystemService(WINDOW_SERVICE) as WindowManager
642646
val params = overlay.layoutParams as WindowManager.LayoutParams
643-
val newHeight = if (locked && lockScreenEnabled) {
644-
getLockScreenOverlayHeight()
647+
val (newHeight, newY) = if (locked && lockScreenEnabled) {
648+
val frame = getLockScreenOverlayFrame()
649+
frame.heightPx to frame.yPx
645650
} else if (statusBarEnabled) {
646-
getStatusBarHeight()
651+
getStatusBarHeight() to 0
647652
} else {
648653
// Only lock screen feature enabled, not on lock screen — keep minimal
649-
getStatusBarHeight()
654+
getStatusBarHeight() to 0
650655
}
651656

652-
if (params.height != newHeight) {
657+
if (params.height != newHeight || params.y != newY) {
653658
params.height = newHeight
659+
params.y = newY
654660
wm.updateViewLayout(overlay, params)
655661
doubleTapDetector.reset()
656-
Log.d(TAG, "overlay: lockScreen=$locked, height=${newHeight}px")
662+
Log.d(TAG, "overlay: lockScreen=$locked, height=${newHeight}px, y=${newY}px")
657663
}
658664

659665
if (locked != wasOnLockScreen) {
@@ -663,11 +669,21 @@ class TapLockAccessibilityService : AccessibilityService() {
663669
updateInteractiveZoneOverlays()
664670
}
665671

666-
private fun getLockScreenOverlayHeight(): Int {
672+
private fun getLockScreenOverlayFrame(): LockZoneFrame {
667673
val wm = getSystemService(WINDOW_SERVICE) as WindowManager
668674
val screenHeight = wm.currentWindowMetrics.bounds.height()
669-
val percent = getPrefs().getInt(getString(R.string.lock_zone_percent), 66)
670-
return (screenHeight * percent) / 100
675+
val prefs = getPrefs()
676+
return TapLockLockZone.buildFrame(
677+
screenHeightPx = screenHeight,
678+
zonePercent = prefs.getInt(
679+
getString(R.string.lock_zone_percent),
680+
TapLockLockZone.DEFAULT_PERCENT
681+
),
682+
topOffsetPercent = prefs.getInt(
683+
getString(R.string.lock_zone_top_offset_percent),
684+
TapLockLockZone.DEFAULT_TOP_OFFSET_PERCENT
685+
)
686+
)
671687
}
672688

673689
private fun handleLockScreenTap(tapTimeMs: Long, x: Float, y: Float) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.ah.taplock
2+
3+
import kotlin.math.roundToInt
4+
5+
data class LockZoneFrame(
6+
val heightPx: Int,
7+
val yPx: Int
8+
)
9+
10+
object TapLockLockZone {
11+
const val DEFAULT_PERCENT = 66
12+
const val MIN_PERCENT = 20
13+
const val MAX_PERCENT = 100
14+
15+
const val DEFAULT_TOP_OFFSET_PERCENT = 0
16+
const val MIN_TOP_OFFSET_PERCENT = 0
17+
18+
fun clampPercent(value: Int): Int = value.coerceIn(MIN_PERCENT, MAX_PERCENT)
19+
20+
fun maxTopOffsetPercent(zonePercent: Int): Int = (100 - clampPercent(zonePercent)).coerceAtLeast(0)
21+
22+
fun clampTopOffsetPercent(
23+
value: Int,
24+
zonePercent: Int
25+
): Int = value.coerceIn(MIN_TOP_OFFSET_PERCENT, maxTopOffsetPercent(zonePercent))
26+
27+
fun buildFrame(
28+
screenHeightPx: Int,
29+
zonePercent: Int,
30+
topOffsetPercent: Int
31+
): LockZoneFrame {
32+
val boundedScreenHeightPx = screenHeightPx.coerceAtLeast(1)
33+
val clampedZonePercent = clampPercent(zonePercent)
34+
val clampedTopOffsetPercent = clampTopOffsetPercent(topOffsetPercent, clampedZonePercent)
35+
val heightPx = ((boundedScreenHeightPx * clampedZonePercent) / 100f)
36+
.roundToInt()
37+
.coerceIn(1, boundedScreenHeightPx)
38+
val yPx = ((boundedScreenHeightPx * clampedTopOffsetPercent) / 100f)
39+
.roundToInt()
40+
.coerceIn(0, (boundedScreenHeightPx - heightPx).coerceAtLeast(0))
41+
42+
return LockZoneFrame(
43+
heightPx = heightPx,
44+
yPx = yPx
45+
)
46+
}
47+
}

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<string name="status_bar_double_tap_description">Double tap on the status bar to lock the screen.</string>
4848
<string name="lock_screen_double_tap">lock_screen_double_tap</string>
4949
<string name="lock_screen_double_tap_label">Double-Tap Lock Screen to Lock</string>
50-
<string name="lock_screen_double_tap_description">When the screen is locked, double-tap on the top two-thirds of the screen to lock.</string>
50+
<string name="lock_screen_double_tap_description">When the screen is locked, double-tap inside the configured area to lock.</string>
5151
<string name="left_edge_lock_zone">left_edge_lock_zone</string>
5252
<string name="right_edge_lock_zone">right_edge_lock_zone</string>
5353
<string name="top_left_corner_lock_zone">top_left_corner_lock_zone</string>
@@ -130,7 +130,9 @@
130130
<string name="quick_settings_tile_failed_toast">Couldn\'t add the Quick Settings tile right now</string>
131131
<string name="quick_settings_tile_manual_toast">Add TapLock from the Quick Settings edit screen on this Android version.</string>
132132
<string name="lock_zone_percent">lock_zone_percent</string>
133+
<string name="lock_zone_top_offset_percent">lock_zone_top_offset_percent</string>
133134
<string name="lock_zone_label">Lock screen tap zone: %d%%</string>
135+
<string name="lock_zone_top_offset_label">Top offset: %d%%</string>
134136
<string name="lock_zone_preview_label">Active area preview</string>
135137
<string name="lock_zone_preview_button">Highlight active area</string>
136138
<string name="edge_zones_label">Screen Edge And Corner Lock Zones</string>

0 commit comments

Comments
 (0)