pakstr 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,11 +3,14 @@ package com.pakstr.app
3
3
  import android.Manifest
4
4
  import android.content.Intent
5
5
  import android.os.Bundle
6
+ import android.os.Handler
7
+ import android.os.Looper
6
8
 
7
9
  import android.view.View
8
10
  import android.webkit.PermissionRequest
9
11
  import android.webkit.WebView
10
12
  import android.widget.FrameLayout
13
+ import android.widget.Toast
11
14
  import androidx.activity.OnBackPressedCallback
12
15
  import androidx.activity.enableEdgeToEdge
13
16
  import androidx.activity.result.ActivityResultLauncher
@@ -22,8 +25,11 @@ import com.pakstr.app.debug.AppDebugLogger
22
25
  import com.pakstr.app.debug.CrashHandler
23
26
 
24
27
  import com.pakstr.app.debug.DebugReportShare
28
+ import com.pakstr.app.debug.DeveloperToolsAccessManager
25
29
  import com.pakstr.app.debug.DeveloperToolsActivity
26
30
  import com.pakstr.app.debug.DeveloperToolsManager
31
+ import com.pakstr.app.debug.SupportUnlock
32
+ import com.pakstr.app.debug.SupportUnlockGestureLayout
27
33
  import com.pakstr.app.debug.WebViewDebugManager
28
34
 
29
35
  import com.pakstr.app.permissions.PermissionManager
@@ -46,6 +52,12 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
46
52
  ActivityResultLauncher<Intent>
47
53
 
48
54
  private lateinit var fab: FloatingActionButton
55
+ private lateinit var supportUnlockGestureLayout: SupportUnlockGestureLayout
56
+
57
+ private val diagnosticsHandler = Handler(Looper.getMainLooper())
58
+ private val diagnosticsExpiryCheck = Runnable {
59
+ showDebugFab()
60
+ }
49
61
 
50
62
  override fun onCreate(
51
63
 
@@ -92,6 +104,7 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
92
104
  )
93
105
 
94
106
  initViews()
107
+ setupSupportUnlockGesture()
95
108
  showDebugFab()
96
109
 
97
110
  amberLauncher =
@@ -151,33 +164,61 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
151
164
  }
152
165
 
153
166
  private fun showDebugFab() {
154
- if (BuildConfig.DEBUG || RuntimeConfig.debugEnabled(this)) {
167
+ diagnosticsHandler.removeCallbacks(diagnosticsExpiryCheck)
168
+
169
+ if (DeveloperToolsAccessManager.canOpenDeveloperTools(this)) {
155
170
 
156
171
  fab.visibility = View.VISIBLE
157
172
 
158
173
  fab.setOnClickListener {
174
+ if (DeveloperToolsAccessManager.canOpenDeveloperTools(this)) {
175
+ startActivity(
159
176
 
160
- startActivity(
177
+ Intent(
161
178
 
162
- Intent(
179
+ this,
163
180
 
164
- this,
181
+ DeveloperToolsActivity::class.java
165
182
 
166
- DeveloperToolsActivity::class.java
183
+ )
167
184
 
168
185
  )
186
+ } else {
187
+ showDebugFab()
188
+ }
189
+ }
169
190
 
191
+ val remainingDuration = SupportUnlock.remainingDuration(this)
192
+ if (!BuildConfig.DEBUG &&
193
+ !RuntimeConfig.debugEnabled(this) &&
194
+ remainingDuration > 0L
195
+ ) {
196
+ diagnosticsHandler.postDelayed(
197
+ diagnosticsExpiryCheck,
198
+ remainingDuration
170
199
  )
171
-
172
200
  }
173
201
 
174
202
  } else {
175
203
 
176
204
  fab.visibility = View.GONE
205
+ fab.setOnClickListener(null)
177
206
 
178
207
  }
179
208
  }
180
209
 
210
+ private fun setupSupportUnlockGesture() {
211
+ supportUnlockGestureLayout.onUnlock = {
212
+ SupportUnlock.unlock(this)
213
+ Toast.makeText(
214
+ this,
215
+ R.string.diagnostics_mode_enabled,
216
+ Toast.LENGTH_LONG
217
+ ).show()
218
+ showDebugFab()
219
+ }
220
+ }
221
+
181
222
  private fun setupBackNavigation() {
182
223
  onBackPressedDispatcher.addCallback(
183
224
  this,
@@ -220,6 +261,9 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
220
261
  container =
221
262
  findViewById(R.id.mainContainer)
222
263
 
264
+ supportUnlockGestureLayout =
265
+ container as SupportUnlockGestureLayout
266
+
223
267
  webView =
224
268
  findViewById(R.id.webView)
225
269
 
@@ -243,7 +287,29 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
243
287
  )
244
288
  }
245
289
 
290
+ override fun onResume() {
291
+ super.onResume()
292
+
293
+ if (::fab.isInitialized) {
294
+ showDebugFab()
295
+ }
296
+ }
297
+
298
+ override fun onStop() {
299
+ if (::supportUnlockGestureLayout.isInitialized) {
300
+ supportUnlockGestureLayout.cancelHold()
301
+ }
302
+
303
+ super.onStop()
304
+ }
305
+
246
306
  override fun onDestroy() {
307
+ diagnosticsHandler.removeCallbacks(diagnosticsExpiryCheck)
308
+
309
+ if (::supportUnlockGestureLayout.isInitialized) {
310
+ supportUnlockGestureLayout.cancelHold()
311
+ supportUnlockGestureLayout.onUnlock = null
312
+ }
247
313
 
248
314
  if (::runtime.isInitialized) {
249
315
 
@@ -0,0 +1,14 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.content.Context
4
+ import com.pakstr.app.BuildConfig
5
+ import com.pakstr.app.RuntimeConfig
6
+
7
+ object DeveloperToolsAccessManager {
8
+
9
+ fun canOpenDeveloperTools(context: Context): Boolean {
10
+ return BuildConfig.DEBUG ||
11
+ RuntimeConfig.debugEnabled(context) ||
12
+ SupportUnlock.isActive(context)
13
+ }
14
+ }
@@ -0,0 +1,44 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.content.Context
4
+
5
+ object SupportUnlock {
6
+
7
+ const val UNLOCK_DURATION_MS = 60 * 60 * 1000L
8
+
9
+ private const val PREFERENCES_NAME = "pakstr_support_unlock"
10
+ private const val EXPIRES_AT_KEY = "expires_at"
11
+
12
+ fun unlock(context: Context) {
13
+ preferences(context)
14
+ .edit()
15
+ .putLong(EXPIRES_AT_KEY, System.currentTimeMillis() + UNLOCK_DURATION_MS)
16
+ .apply()
17
+ }
18
+
19
+ fun isActive(context: Context): Boolean {
20
+ val remainingDuration = remainingDuration(context)
21
+
22
+ if (remainingDuration <= 0L) {
23
+ clear(context)
24
+ return false
25
+ }
26
+
27
+ return true
28
+ }
29
+
30
+ fun clear(context: Context) {
31
+ preferences(context)
32
+ .edit()
33
+ .remove(EXPIRES_AT_KEY)
34
+ .apply()
35
+ }
36
+
37
+ fun remainingDuration(context: Context): Long {
38
+ val expiresAt = preferences(context).getLong(EXPIRES_AT_KEY, 0L)
39
+ return (expiresAt - System.currentTimeMillis()).coerceAtLeast(0L)
40
+ }
41
+
42
+ private fun preferences(context: Context) =
43
+ context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
44
+ }
@@ -0,0 +1,87 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.content.Context
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.util.AttributeSet
7
+ import android.view.MotionEvent
8
+ import android.view.ViewConfiguration
9
+ import android.widget.FrameLayout
10
+ import kotlin.math.abs
11
+
12
+ class SupportUnlockGestureLayout @JvmOverloads constructor(
13
+ context: Context,
14
+ attrs: AttributeSet? = null,
15
+ defStyleAttr: Int = 0
16
+ ) : FrameLayout(context, attrs, defStyleAttr) {
17
+
18
+ var onUnlock: (() -> Unit)? = null
19
+
20
+ private val handler = Handler(Looper.getMainLooper())
21
+ private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
22
+ private val cornerSize = CORNER_SIZE_DP * resources.displayMetrics.density
23
+
24
+ private var tracking = false
25
+ private var downX = 0f
26
+ private var downY = 0f
27
+
28
+ private val unlockRunnable = Runnable {
29
+ if (tracking) {
30
+ tracking = false
31
+ onUnlock?.invoke()
32
+ }
33
+ }
34
+
35
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
36
+ observeTouch(event)
37
+ return super.dispatchTouchEvent(event)
38
+ }
39
+
40
+ fun cancelHold() {
41
+ tracking = false
42
+ handler.removeCallbacks(unlockRunnable)
43
+ }
44
+
45
+ override fun onDetachedFromWindow() {
46
+ cancelHold()
47
+ super.onDetachedFromWindow()
48
+ }
49
+
50
+ private fun observeTouch(event: MotionEvent) {
51
+ when (event.actionMasked) {
52
+ MotionEvent.ACTION_DOWN -> {
53
+ if (isInTopRightCorner(event.x, event.y)) {
54
+ tracking = true
55
+ downX = event.x
56
+ downY = event.y
57
+ handler.postDelayed(unlockRunnable, HOLD_DURATION_MS)
58
+ } else {
59
+ cancelHold()
60
+ }
61
+ }
62
+
63
+ MotionEvent.ACTION_MOVE -> {
64
+ if (tracking && hasMovedBeyondTouchSlop(event.x, event.y)) {
65
+ cancelHold()
66
+ }
67
+ }
68
+
69
+ MotionEvent.ACTION_POINTER_DOWN,
70
+ MotionEvent.ACTION_UP,
71
+ MotionEvent.ACTION_CANCEL -> cancelHold()
72
+ }
73
+ }
74
+
75
+ private fun isInTopRightCorner(x: Float, y: Float): Boolean {
76
+ return x >= width - cornerSize && y <= paddingTop + cornerSize
77
+ }
78
+
79
+ private fun hasMovedBeyondTouchSlop(x: Float, y: Float): Boolean {
80
+ return abs(x - downX) > touchSlop || abs(y - downY) > touchSlop
81
+ }
82
+
83
+ private companion object {
84
+ const val HOLD_DURATION_MS = 10_000L
85
+ const val CORNER_SIZE_DP = 72
86
+ }
87
+ }
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
+ <com.pakstr.app.debug.SupportUnlockGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  android:id="@+id/mainContainer"
4
4
  android:layout_width="match_parent"
5
5
  android:layout_height="match_parent"
@@ -27,4 +27,4 @@
27
27
  android:contentDescription="@string/developer_tools"
28
28
  android:src="@android:drawable/ic_menu_info_details" />
29
29
 
30
- </FrameLayout>
30
+ </com.pakstr.app.debug.SupportUnlockGestureLayout>
@@ -43,4 +43,5 @@
43
43
  <string name="developer_tools_browser_unavailable">No browser is available.</string>
44
44
 
45
45
  <string name="developer_tools">Open Developer Tools</string>
46
+ <string name="diagnostics_mode_enabled">Diagnostics mode enabled</string>
46
47
  </resources>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for packaging Nostr web apps into Android APKs",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",