Skip to content

Commit 6bc5d17

Browse files
authored
Merge pull request #2 from FeernandoOFF/feature/shake-to-open
Feature/shake to open
2 parents 7a363cc + 22d2455 commit 6bc5d17

8 files changed

Lines changed: 220 additions & 8 deletions

File tree

.idea/deviceManager.xml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/dictionaries/project.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ inspect DataStore preferences, monitor logs, and create custom debugging modules
1010
Currently, there are a couple of other debug menu libraries,
1111
like [Lens-Logger](https://github.com/farhazulMullick/Lens-Logger/tree/feat/log-datastore)
1212
and [Beagle](https://github.com/pandulapeter/beagle), however, they were either focused on a limited set of features or
13-
interfered with the LayoutInpector in Android Studio.
13+
interfered with the LayoutInspector in Android Studio.
1414

1515
This library offers a lightweight, easy-to-use, and modular debug menu that you can easily integrate into your app and
1616
customise to your needs.
@@ -33,7 +33,7 @@ customise to your needs.
3333

3434
### Basic Usage
3535

36-
In order to use the library, you first need to add the FAB to your Activity/Composable; this changes depending on the
36+
To use the library, you first need to add the FAB to your Activity/Composable; this changes depending on the
3737
project.
3838

3939
<details>
@@ -111,6 +111,38 @@ class MainActivity : ComponentActivity() {
111111

112112
</details>
113113

114+
### Showing the Debug Menu
115+
116+
<details>
117+
<summary>FAB Button</summary>
118+
With all the usage methods above, you can also pass a `showFab` parameter to show or hide the FAB button.
119+
120+
```kotlin
121+
DebugMenuOverlay(
122+
showFab = true, // <-- Only open through the FAB button
123+
enableShake = false, // <-- Disable shake to open
124+
modules = listOf(
125+
// your modules...
126+
)
127+
)
128+
```
129+
</details>
130+
131+
<details>
132+
<summary> Shake to Open </summary>
133+
If you only want the menu to be opened through shake, you can pass `enableShake` to `true` and disable the FAB button.
134+
135+
```kotlin
136+
DebugMenuOverlay(
137+
showFab = false, // <-- Disable FAB button
138+
enableShake = true, // <-- Enable shake to open
139+
modules = listOf(
140+
// your modules...
141+
)
142+
)
143+
```
144+
</details>
145+
114146
## Integrating Modules
115147

116148
> Note: Modules determine the order in which they are displayed in the debug menu.
@@ -121,7 +153,8 @@ class MainActivity : ComponentActivity() {
121153

122154
**Adding Analytics Module**
123155

124-
Just add the `DebugAnalytics` singleton to your list of Modules, and you're good to go.
156+
First, to show the module, you need to add the `AnalyticsModule` to your list of Modules. Then, you can use the
157+
`DebugAnalytics` singleton to log events.
125158

126159
```kotlin
127160
DebugMenuOverlay(
@@ -140,7 +173,7 @@ just add the event to the `DebugAnalytics` singleton, and it will be logged in t
140173

141174
> **Note:** The signature of the `logEvent` method is the same as Firebase Analytics, so if you're using Firebase
142175
> Analytics,
143-
> you can just call it with the same signature.
176+
> you can just call it in the same way.
144177
145178
```kotlin
146179
class AnalyticsManager {
@@ -161,7 +194,7 @@ class AnalyticsManager {
161194

162195
**Adding DataStore Module**
163196

164-
Just add the `DataStoreModule` class to your list of Modules, and pass the list of DataStores and you're good to go. The
197+
Just add the `DataStoreModule` class to your list of Modules, and pass the list of DataStores and, you're good to go. The
165198
UI Will automatically generate the UI for every entry.
166199

167200
```kotlin
@@ -261,7 +294,7 @@ class DemoApp : Application() {
261294
<br/>
262295
The dynamic module allows you to add custom actions to the debug menu. They can either be:
263296

264-
- Global actions: These actions are displayed in the debug menu, and can be triggered from anywhere in the app.
297+
- Global actions: These actions are displayed in the debug menu and can be triggered from anywhere in the app.
265298
- Dynamic Actions: These actions are only displayed when the user is in a specific screen and get automatically removed
266299
when the user navigates away from that screen.
267300

debugMenu/src/main/java/com/tapadoo/debugmenu/DebugMenu.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.compose.material3.dynamicLightColorScheme
2727
import androidx.compose.material3.lightColorScheme
2828
import androidx.compose.material3.rememberModalBottomSheetState
2929
import androidx.compose.runtime.Composable
30+
import androidx.compose.runtime.DisposableEffect
3031
import androidx.compose.runtime.derivedStateOf
3132
import androidx.compose.runtime.getValue
3233
import androidx.compose.runtime.mutableStateOf
@@ -35,7 +36,9 @@ import androidx.compose.runtime.rememberCoroutineScope
3536
import androidx.compose.runtime.setValue
3637
import androidx.compose.ui.Alignment
3738
import androidx.compose.ui.Modifier
39+
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3840
import androidx.compose.ui.platform.LocalContext
41+
import androidx.compose.ui.platform.LocalHapticFeedback
3942
import androidx.compose.ui.unit.dp
4043
import com.tapadoo.debugmenu.analytics.AnalyticsModule
4144
import com.tapadoo.debugmenu.module.DebugMenuModule
@@ -48,10 +51,33 @@ fun DebugMenuOverlay(
4851
modules: List<DebugMenuModule> = listOf(AnalyticsModule()),
4952
modifier: Modifier = Modifier,
5053
showFab: Boolean = true,
54+
enableShake: Boolean = false,
5155
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
5256
colorScheme: ColorScheme = getTheme()
5357
) {
58+
val haptics = LocalHapticFeedback.current
59+
val context = LocalContext.current
60+
61+
5462
var showContent by remember { mutableStateOf(false) }
63+
64+
// Shake detector
65+
if (enableShake) {
66+
val shakeDetector = remember {
67+
ShakeDetector(context) {
68+
showContent = true
69+
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
70+
}
71+
}
72+
73+
DisposableEffect(Unit) {
74+
shakeDetector.start()
75+
onDispose {
76+
shakeDetector.stop()
77+
}
78+
}
79+
}
80+
5581
MaterialTheme(
5682
colorScheme = colorScheme,
5783
) {

debugMenu/src/main/java/com/tapadoo/debugmenu/DebugMenuAttacher.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ object DebugMenuAttacher {
2020
fun attachToApplication(
2121
application: Application,
2222
modules: List<DebugMenuModule>,
23+
showFab: Boolean = true,
24+
enableShake: Boolean = false,
2325
) {
2426
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
2527
override fun onActivityCreated(activity: Activity, savedInstanceState: android.os.Bundle?) {
26-
DebugMenuAttacher.attach(activity, modules)
28+
try {
29+
// When attaching to an app that uses an Activity as a splash screen, it can crash
30+
DebugMenuAttacher.attach(activity, modules, showFab, enableShake)
31+
} catch (e: Exception) {
32+
33+
}
2734
}
2835

2936
override fun onActivityStarted(activity: Activity) {}
@@ -41,6 +48,8 @@ object DebugMenuAttacher {
4148
fun attach(
4249
activity: Activity,
4350
modules: List<DebugMenuModule>,
51+
showFab: Boolean = true,
52+
enableShake: Boolean = false,
4453
) = runCatching {
4554
val decor = activity.window?.decorView as? ViewGroup ?: return@runCatching
4655
// Avoid duplicates
@@ -66,7 +75,8 @@ object DebugMenuAttacher {
6675
)
6776
setContent {
6877
DebugMenuOverlay(
69-
showFab = true,
78+
showFab = showFab,
79+
enableShake = enableShake,
7080
modules = modules,
7181
)
7282
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.tapadoo.debugmenu
2+
3+
import android.content.Context
4+
import android.hardware.Sensor
5+
import android.hardware.SensorEvent
6+
import android.hardware.SensorEventListener
7+
import android.hardware.SensorManager
8+
import kotlin.math.sqrt
9+
10+
class ShakeDetector(
11+
context: Context,
12+
private val onShake: () -> Unit
13+
) : SensorEventListener {
14+
15+
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
16+
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
17+
18+
private var lastShakeTime: Long = 0
19+
private var lastX = 0f
20+
private var lastY = 0f
21+
private var lastZ = 0f
22+
23+
companion object {
24+
private const val SHAKE_THRESHOLD = 15f
25+
private const val SHAKE_COOLDOWN_MS = 1000L
26+
}
27+
28+
fun start() {
29+
accelerometer?.let {
30+
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
31+
}
32+
}
33+
34+
fun stop() {
35+
sensorManager.unregisterListener(this)
36+
}
37+
38+
override fun onSensorChanged(event: SensorEvent?) {
39+
if (event?.sensor?.type != Sensor.TYPE_ACCELEROMETER) return
40+
41+
val currentTime = System.currentTimeMillis()
42+
if (currentTime - lastShakeTime < SHAKE_COOLDOWN_MS) return
43+
44+
val x = event.values[0]
45+
val y = event.values[1]
46+
val z = event.values[2]
47+
48+
val deltaX = x - lastX
49+
val deltaY = y - lastY
50+
val deltaZ = z - lastZ
51+
52+
lastX = x
53+
lastY = y
54+
lastZ = z
55+
56+
val acceleration = sqrt((deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ).toDouble())
57+
58+
if (acceleration > SHAKE_THRESHOLD) {
59+
lastShakeTime = currentTime
60+
onShake()
61+
}
62+
}
63+
64+
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
65+
// Not needed for shake detection
66+
}
67+
}

0 commit comments

Comments
 (0)