pakstr 0.5.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.
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <application>
4
+ <activity-alias
5
+ android:name=".debug.DeveloperToolsDebugLauncher"
6
+ android:enabled="true"
7
+ android:exported="true"
8
+ android:label="@string/developer_tools_title"
9
+ android:targetActivity=".debug.DeveloperToolsActivity" />
10
+ </application>
11
+ </manifest>
@@ -28,6 +28,12 @@
28
28
  android:supportsRtl="true"
29
29
  android:usesCleartextTraffic="true">
30
30
 
31
+ <activity
32
+ android:name=".debug.DeveloperToolsActivity"
33
+ android:exported="false"
34
+ android:label="@string/developer_tools_title"
35
+ android:theme="@style/Theme.NostrAppShell" />
36
+
31
37
  <activity
32
38
  android:name=".MainActivity"
33
39
  android:exported="true"
@@ -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
@@ -17,11 +20,16 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
17
20
  import androidx.core.view.ViewCompat
18
21
  import androidx.core.view.WindowInsetsCompat
19
22
  import androidx.core.view.updatePadding
23
+ import com.google.android.material.floatingactionbutton.FloatingActionButton
20
24
  import com.pakstr.app.debug.AppDebugLogger
21
25
  import com.pakstr.app.debug.CrashHandler
22
26
 
23
27
  import com.pakstr.app.debug.DebugReportShare
28
+ import com.pakstr.app.debug.DeveloperToolsAccessManager
29
+ import com.pakstr.app.debug.DeveloperToolsActivity
24
30
  import com.pakstr.app.debug.DeveloperToolsManager
31
+ import com.pakstr.app.debug.SupportUnlock
32
+ import com.pakstr.app.debug.SupportUnlockGestureLayout
25
33
  import com.pakstr.app.debug.WebViewDebugManager
26
34
 
27
35
  import com.pakstr.app.permissions.PermissionManager
@@ -43,6 +51,14 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
43
51
  private lateinit var amberLauncher:
44
52
  ActivityResultLauncher<Intent>
45
53
 
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
+ }
61
+
46
62
  override fun onCreate(
47
63
 
48
64
  savedInstanceState: Bundle?
@@ -88,6 +104,8 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
88
104
  )
89
105
 
90
106
  initViews()
107
+ setupSupportUnlockGesture()
108
+ showDebugFab()
91
109
 
92
110
  amberLauncher =
93
111
 
@@ -145,6 +163,62 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
145
163
 
146
164
  }
147
165
 
166
+ private fun showDebugFab() {
167
+ diagnosticsHandler.removeCallbacks(diagnosticsExpiryCheck)
168
+
169
+ if (DeveloperToolsAccessManager.canOpenDeveloperTools(this)) {
170
+
171
+ fab.visibility = View.VISIBLE
172
+
173
+ fab.setOnClickListener {
174
+ if (DeveloperToolsAccessManager.canOpenDeveloperTools(this)) {
175
+ startActivity(
176
+
177
+ Intent(
178
+
179
+ this,
180
+
181
+ DeveloperToolsActivity::class.java
182
+
183
+ )
184
+
185
+ )
186
+ } else {
187
+ showDebugFab()
188
+ }
189
+ }
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
199
+ )
200
+ }
201
+
202
+ } else {
203
+
204
+ fab.visibility = View.GONE
205
+ fab.setOnClickListener(null)
206
+
207
+ }
208
+ }
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
+
148
222
  private fun setupBackNavigation() {
149
223
  onBackPressedDispatcher.addCallback(
150
224
  this,
@@ -187,6 +261,9 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
187
261
  container =
188
262
  findViewById(R.id.mainContainer)
189
263
 
264
+ supportUnlockGestureLayout =
265
+ container as SupportUnlockGestureLayout
266
+
190
267
  webView =
191
268
  findViewById(R.id.webView)
192
269
 
@@ -197,6 +274,10 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
197
274
  permissionManager =
198
275
  PermissionManager(this)
199
276
 
277
+ fab = findViewById(
278
+
279
+ R.id.developerToolsFab)
280
+
200
281
  }
201
282
 
202
283
  private fun handleSignerSetup() {
@@ -206,7 +287,29 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
206
287
  )
207
288
  }
208
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
+
209
306
  override fun onDestroy() {
307
+ diagnosticsHandler.removeCallbacks(diagnosticsExpiryCheck)
308
+
309
+ if (::supportUnlockGestureLayout.isInitialized) {
310
+ supportUnlockGestureLayout.cancelHold()
311
+ supportUnlockGestureLayout.onUnlock = null
312
+ }
210
313
 
211
314
  if (::runtime.isInitialized) {
212
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,234 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.content.ActivityNotFoundException
4
+ import android.content.Intent
5
+ import android.graphics.Typeface
6
+ import android.net.Uri
7
+ import android.os.Build
8
+ import android.os.Bundle
9
+ import android.os.Handler
10
+ import android.os.Looper
11
+ import android.text.format.DateUtils
12
+ import android.view.ViewGroup
13
+ import android.widget.ScrollView
14
+ import android.widget.TextView
15
+ import android.widget.Toast
16
+ import androidx.activity.enableEdgeToEdge
17
+ import androidx.appcompat.app.AppCompatActivity
18
+ import com.google.android.material.appbar.MaterialToolbar
19
+ import com.google.android.material.button.MaterialButton
20
+ import com.google.android.material.dialog.MaterialAlertDialogBuilder
21
+ import com.google.android.material.switchmaterial.SwitchMaterial
22
+ import com.pakstr.app.BuildConfig
23
+ import com.pakstr.app.R
24
+ import com.pakstr.app.RuntimeConfig
25
+
26
+ class DeveloperToolsActivity : AppCompatActivity() {
27
+
28
+ private val uptimeHandler = Handler(Looper.getMainLooper())
29
+
30
+ private lateinit var uptimeValue: TextView
31
+ private lateinit var debugModeSwitch: SwitchMaterial
32
+ private lateinit var apiSwitch: SwitchMaterial
33
+ private lateinit var debugWebViewStatus: TextView
34
+ private lateinit var webViewStatus: TextView
35
+
36
+ private val uptimeUpdater = object : Runnable {
37
+ override fun run() {
38
+ uptimeValue.text = DateUtils.formatElapsedTime(DebugSession.uptime() / 1000)
39
+ uptimeHandler.postDelayed(this, UPTIME_REFRESH_INTERVAL_MS)
40
+ }
41
+ }
42
+
43
+ override fun onCreate(savedInstanceState: Bundle?) {
44
+ enableEdgeToEdge()
45
+ super.onCreate(savedInstanceState)
46
+ setContentView(R.layout.activity_developer_tools)
47
+
48
+ setupToolbar()
49
+ bindViews()
50
+ populateRuntimeDetails()
51
+ setupDebugControls()
52
+ setupLogActions()
53
+ setupCrashTesting()
54
+ setupWebViewActions()
55
+ }
56
+
57
+ override fun onStart() {
58
+ super.onStart()
59
+ refreshStatuses()
60
+ uptimeHandler.post(uptimeUpdater)
61
+ }
62
+
63
+ override fun onStop() {
64
+ uptimeHandler.removeCallbacks(uptimeUpdater)
65
+ super.onStop()
66
+ }
67
+
68
+ private fun setupToolbar() {
69
+ findViewById<MaterialToolbar>(R.id.developerToolsToolbar).setNavigationOnClickListener {
70
+ finish()
71
+ }
72
+ }
73
+
74
+ private fun bindViews() {
75
+ uptimeValue = findViewById(R.id.runtimeUptimeValue)
76
+ debugModeSwitch = findViewById(R.id.debugModeSwitch)
77
+ apiSwitch = findViewById(R.id.apiEnabledSwitch)
78
+ debugWebViewStatus = findViewById(R.id.debugWebViewStatus)
79
+ webViewStatus = findViewById(R.id.webViewStatus)
80
+ }
81
+
82
+ private fun populateRuntimeDetails() {
83
+ findViewById<TextView>(R.id.runtimeSessionIdValue).text = DebugSession.sessionId
84
+ findViewById<TextView>(R.id.runtimeAppVersionValue).text = getString(
85
+ R.string.developer_tools_version_value,
86
+ BuildConfig.VERSION_NAME,
87
+ BuildConfig.VERSION_CODE
88
+ )
89
+ findViewById<TextView>(R.id.runtimeAndroidVersionValue).text = Build.VERSION.RELEASE
90
+ findViewById<TextView>(R.id.runtimeDeviceModelValue).text = getString(
91
+ R.string.developer_tools_device_value,
92
+ Build.MANUFACTURER,
93
+ Build.MODEL
94
+ )
95
+ }
96
+
97
+ private fun setupDebugControls() {
98
+ apiSwitch.isChecked = RuntimeConfig.apiEnabled(this)
99
+
100
+ debugModeSwitch.setOnCheckedChangeListener { _, isChecked ->
101
+ if (isChecked) {
102
+ DeveloperToolsManager.enableDebug(this)
103
+ } else {
104
+ DeveloperToolsManager.disableDebug(this)
105
+ }
106
+
107
+ WebViewDebugManager.enable(this)
108
+ refreshStatuses()
109
+ }
110
+ }
111
+
112
+ private fun refreshStatuses() {
113
+ debugModeSwitch.isChecked = DeveloperToolsManager.isEnabled(this)
114
+ apiSwitch.isChecked = RuntimeConfig.apiEnabled(this)
115
+
116
+ val status = if (isWebViewDebugEnabled()) {
117
+ R.string.developer_tools_enabled
118
+ } else {
119
+ R.string.developer_tools_disabled
120
+ }
121
+ debugWebViewStatus.setText(status)
122
+ webViewStatus.setText(status)
123
+ }
124
+
125
+ private fun isWebViewDebugEnabled(): Boolean {
126
+ return BuildConfig.DEBUG || RuntimeConfig.debugEnabled(this)
127
+ }
128
+
129
+ private fun setupLogActions() {
130
+ findViewById<MaterialButton>(R.id.viewLogsButton).setOnClickListener {
131
+ showLogs()
132
+ }
133
+ findViewById<MaterialButton>(R.id.clearLogsButton).setOnClickListener {
134
+ confirmClearLogs()
135
+ }
136
+ findViewById<MaterialButton>(R.id.exportDebugReportButton).setOnClickListener {
137
+ exportDebugReport()
138
+ }
139
+ findViewById<MaterialButton>(R.id.shareReportButton).setOnClickListener {
140
+ DebugReportShare.share(this)
141
+ }
142
+ }
143
+
144
+ private fun showLogs() {
145
+ val logs = DebugLogStorage.read(this)
146
+ val content = if (logs.isEmpty()) {
147
+ getString(R.string.developer_tools_no_logs)
148
+ } else {
149
+ logs.joinToString("\n")
150
+ }
151
+
152
+ val textView = TextView(this).apply {
153
+ text = content
154
+ typeface = Typeface.MONOSPACE
155
+ setTextIsSelectable(true)
156
+ val padding = resources.getDimensionPixelSize(
157
+ R.dimen.developer_tools_dialog_padding
158
+ )
159
+ setPadding(padding, padding, padding, padding)
160
+ }
161
+
162
+ val scrollView = ScrollView(this).apply {
163
+ addView(textView)
164
+ }
165
+
166
+ MaterialAlertDialogBuilder(this)
167
+ .setTitle(R.string.developer_tools_view_logs)
168
+ .setView(scrollView)
169
+ .setPositiveButton(android.R.string.ok, null)
170
+ .show()
171
+ .window
172
+ ?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
173
+ }
174
+
175
+ private fun confirmClearLogs() {
176
+ MaterialAlertDialogBuilder(this)
177
+ .setTitle(R.string.developer_tools_clear_logs)
178
+ .setMessage(R.string.developer_tools_clear_logs_confirmation)
179
+ .setNegativeButton(android.R.string.cancel, null)
180
+ .setPositiveButton(R.string.developer_tools_clear) { _, _ ->
181
+ AppDebugLogger.clear(this)
182
+ showMessage(R.string.developer_tools_logs_cleared)
183
+ }
184
+ .show()
185
+ }
186
+
187
+ private fun exportDebugReport() {
188
+ runCatching {
189
+ DebugReportExporter.export(this)
190
+ }.onSuccess { report ->
191
+ Toast.makeText(
192
+ this,
193
+ getString(R.string.developer_tools_report_exported, report.absolutePath),
194
+ Toast.LENGTH_LONG
195
+ ).show()
196
+ }.onFailure {
197
+ showMessage(R.string.developer_tools_report_export_failed)
198
+ }
199
+ }
200
+
201
+ private fun setupCrashTesting() {
202
+ findViewById<MaterialButton>(R.id.throwTestExceptionButton).setOnClickListener {
203
+ MaterialAlertDialogBuilder(this)
204
+ .setTitle(R.string.developer_tools_crash_confirmation_title)
205
+ .setMessage(R.string.developer_tools_crash_confirmation_message)
206
+ .setNegativeButton(android.R.string.cancel, null)
207
+ .setPositiveButton(R.string.developer_tools_crash) { _, _ ->
208
+ throw RuntimeException(TEST_EXCEPTION_MESSAGE)
209
+ }
210
+ .show()
211
+ }
212
+ }
213
+
214
+ private fun setupWebViewActions() {
215
+ findViewById<MaterialButton>(R.id.openChromeInspectDocsButton).setOnClickListener {
216
+ try {
217
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(CHROME_INSPECT_DOCS_URL)))
218
+ } catch (_: ActivityNotFoundException) {
219
+ showMessage(R.string.developer_tools_browser_unavailable)
220
+ }
221
+ }
222
+ }
223
+
224
+ private fun showMessage(message: Int) {
225
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
226
+ }
227
+
228
+ private companion object {
229
+ const val UPTIME_REFRESH_INTERVAL_MS = 1000L
230
+ const val TEST_EXCEPTION_MESSAGE = "Developer Tools test exception"
231
+ const val CHROME_INSPECT_DOCS_URL =
232
+ "https://developer.chrome.com/docs/devtools/remote-debugging/webviews/"
233
+ }
234
+ }
@@ -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
+ }
@@ -0,0 +1,308 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ android:layout_width="match_parent"
5
+ android:layout_height="match_parent"
6
+ android:paddingBottom="56dp"
7
+ android:background="?android:attr/colorBackground"
8
+ android:orientation="vertical">
9
+
10
+ <com.google.android.material.appbar.MaterialToolbar
11
+ android:id="@+id/developerToolsToolbar"
12
+ android:layout_width="match_parent"
13
+ android:layout_height="?attr/actionBarSize"
14
+ android:background="?attr/colorSurface"
15
+ android:elevation="4dp"
16
+ app:navigationIcon="?attr/homeAsUpIndicator"
17
+ app:title="@string/developer_tools_title" />
18
+
19
+ <androidx.core.widget.NestedScrollView
20
+ android:layout_width="match_parent"
21
+ android:layout_height="0dp"
22
+ android:layout_weight="1"
23
+ android:fillViewport="true">
24
+
25
+ <LinearLayout
26
+ android:layout_width="match_parent"
27
+ android:layout_height="wrap_content"
28
+ android:orientation="vertical"
29
+ android:padding="16dp">
30
+
31
+ <com.google.android.material.card.MaterialCardView
32
+ android:layout_width="match_parent"
33
+ android:layout_height="wrap_content"
34
+ android:layout_marginBottom="16dp"
35
+ app:cardCornerRadius="12dp"
36
+ app:cardElevation="1dp">
37
+
38
+ <LinearLayout
39
+ android:layout_width="match_parent"
40
+ android:layout_height="wrap_content"
41
+ android:orientation="vertical"
42
+ android:padding="16dp">
43
+
44
+ <TextView
45
+ style="@style/TextAppearance.MaterialComponents.Headline6"
46
+ android:layout_width="wrap_content"
47
+ android:layout_height="wrap_content"
48
+ android:layout_marginBottom="16dp"
49
+ android:text="@string/developer_tools_runtime" />
50
+
51
+ <TextView
52
+ style="@style/TextAppearance.MaterialComponents.Caption"
53
+ android:layout_width="wrap_content"
54
+ android:layout_height="wrap_content"
55
+ android:text="@string/developer_tools_session_id" />
56
+
57
+ <TextView
58
+ android:id="@+id/runtimeSessionIdValue"
59
+ style="@style/TextAppearance.MaterialComponents.Body2"
60
+ android:layout_width="match_parent"
61
+ android:layout_height="wrap_content"
62
+ android:layout_marginBottom="12dp"
63
+ android:textIsSelectable="true" />
64
+
65
+ <TextView
66
+ style="@style/TextAppearance.MaterialComponents.Caption"
67
+ android:layout_width="wrap_content"
68
+ android:layout_height="wrap_content"
69
+ android:text="@string/developer_tools_app_version" />
70
+
71
+ <TextView
72
+ android:id="@+id/runtimeAppVersionValue"
73
+ style="@style/TextAppearance.MaterialComponents.Body2"
74
+ android:layout_width="match_parent"
75
+ android:layout_height="wrap_content"
76
+ android:layout_marginBottom="12dp" />
77
+
78
+ <TextView
79
+ style="@style/TextAppearance.MaterialComponents.Caption"
80
+ android:layout_width="wrap_content"
81
+ android:layout_height="wrap_content"
82
+ android:text="@string/developer_tools_android_version" />
83
+
84
+ <TextView
85
+ android:id="@+id/runtimeAndroidVersionValue"
86
+ style="@style/TextAppearance.MaterialComponents.Body2"
87
+ android:layout_width="match_parent"
88
+ android:layout_height="wrap_content"
89
+ android:layout_marginBottom="12dp" />
90
+
91
+ <TextView
92
+ style="@style/TextAppearance.MaterialComponents.Caption"
93
+ android:layout_width="wrap_content"
94
+ android:layout_height="wrap_content"
95
+ android:text="@string/developer_tools_device_model" />
96
+
97
+ <TextView
98
+ android:id="@+id/runtimeDeviceModelValue"
99
+ style="@style/TextAppearance.MaterialComponents.Body2"
100
+ android:layout_width="match_parent"
101
+ android:layout_height="wrap_content"
102
+ android:layout_marginBottom="12dp" />
103
+
104
+ <TextView
105
+ style="@style/TextAppearance.MaterialComponents.Caption"
106
+ android:layout_width="wrap_content"
107
+ android:layout_height="wrap_content"
108
+ android:text="@string/developer_tools_app_uptime" />
109
+
110
+ <TextView
111
+ android:id="@+id/runtimeUptimeValue"
112
+ style="@style/TextAppearance.MaterialComponents.Body2"
113
+ android:layout_width="match_parent"
114
+ android:layout_height="wrap_content" />
115
+ </LinearLayout>
116
+ </com.google.android.material.card.MaterialCardView>
117
+
118
+ <com.google.android.material.card.MaterialCardView
119
+ android:layout_width="match_parent"
120
+ android:layout_height="wrap_content"
121
+ android:layout_marginBottom="16dp"
122
+ app:cardCornerRadius="12dp"
123
+ app:cardElevation="1dp">
124
+
125
+ <LinearLayout
126
+ android:layout_width="match_parent"
127
+ android:layout_height="wrap_content"
128
+ android:orientation="vertical"
129
+ android:padding="16dp">
130
+
131
+ <TextView
132
+ style="@style/TextAppearance.MaterialComponents.Headline6"
133
+ android:layout_width="wrap_content"
134
+ android:layout_height="wrap_content"
135
+ android:layout_marginBottom="8dp"
136
+ android:text="@string/developer_tools_debug" />
137
+
138
+ <com.google.android.material.switchmaterial.SwitchMaterial
139
+ android:id="@+id/debugModeSwitch"
140
+ android:layout_width="match_parent"
141
+ android:layout_height="wrap_content"
142
+ android:text="@string/developer_tools_enable_debug_mode" />
143
+
144
+ <com.google.android.material.switchmaterial.SwitchMaterial
145
+ android:id="@+id/apiEnabledSwitch"
146
+ android:layout_width="match_parent"
147
+ android:layout_height="wrap_content"
148
+ android:enabled="false"
149
+ android:text="@string/developer_tools_enable_api" />
150
+
151
+ <TextView
152
+ style="@style/TextAppearance.MaterialComponents.Caption"
153
+ android:layout_width="match_parent"
154
+ android:layout_height="wrap_content"
155
+ android:layout_marginStart="4dp"
156
+ android:layout_marginBottom="16dp"
157
+ android:text="@string/developer_tools_api_read_only" />
158
+
159
+ <LinearLayout
160
+ android:layout_width="match_parent"
161
+ android:layout_height="wrap_content"
162
+ android:gravity="center_vertical"
163
+ android:orientation="horizontal">
164
+
165
+ <TextView
166
+ style="@style/TextAppearance.MaterialComponents.Body1"
167
+ android:layout_width="0dp"
168
+ android:layout_height="wrap_content"
169
+ android:layout_weight="1"
170
+ android:text="@string/developer_tools_webview_debug" />
171
+
172
+ <TextView
173
+ android:id="@+id/debugWebViewStatus"
174
+ style="@style/TextAppearance.MaterialComponents.Body2"
175
+ android:layout_width="wrap_content"
176
+ android:layout_height="wrap_content" />
177
+ </LinearLayout>
178
+ </LinearLayout>
179
+ </com.google.android.material.card.MaterialCardView>
180
+
181
+ <com.google.android.material.card.MaterialCardView
182
+ android:layout_width="match_parent"
183
+ android:layout_height="wrap_content"
184
+ android:layout_marginBottom="16dp"
185
+ app:cardCornerRadius="12dp"
186
+ app:cardElevation="1dp">
187
+
188
+ <LinearLayout
189
+ android:layout_width="match_parent"
190
+ android:layout_height="wrap_content"
191
+ android:orientation="vertical"
192
+ android:padding="16dp">
193
+
194
+ <TextView
195
+ style="@style/TextAppearance.MaterialComponents.Headline6"
196
+ android:layout_width="wrap_content"
197
+ android:layout_height="wrap_content"
198
+ android:layout_marginBottom="8dp"
199
+ android:text="@string/developer_tools_logs" />
200
+
201
+ <com.google.android.material.button.MaterialButton
202
+ android:id="@+id/viewLogsButton"
203
+ style="@style/Widget.MaterialComponents.Button.OutlinedButton"
204
+ android:layout_width="match_parent"
205
+ android:layout_height="wrap_content"
206
+ android:text="@string/developer_tools_view_logs" />
207
+
208
+ <com.google.android.material.button.MaterialButton
209
+ android:id="@+id/clearLogsButton"
210
+ style="@style/Widget.MaterialComponents.Button.OutlinedButton"
211
+ android:layout_width="match_parent"
212
+ android:layout_height="wrap_content"
213
+ android:text="@string/developer_tools_clear_logs" />
214
+
215
+ <com.google.android.material.button.MaterialButton
216
+ android:id="@+id/exportDebugReportButton"
217
+ android:layout_width="match_parent"
218
+ android:layout_height="wrap_content"
219
+ android:text="@string/developer_tools_export_debug_report" />
220
+
221
+ <com.google.android.material.button.MaterialButton
222
+ android:id="@+id/shareReportButton"
223
+ android:layout_width="match_parent"
224
+ android:layout_height="wrap_content"
225
+ android:text="@string/developer_tools_share_report" />
226
+ </LinearLayout>
227
+ </com.google.android.material.card.MaterialCardView>
228
+
229
+ <com.google.android.material.card.MaterialCardView
230
+ android:layout_width="match_parent"
231
+ android:layout_height="wrap_content"
232
+ android:layout_marginBottom="16dp"
233
+ app:cardCornerRadius="12dp"
234
+ app:cardElevation="1dp">
235
+
236
+ <LinearLayout
237
+ android:layout_width="match_parent"
238
+ android:layout_height="wrap_content"
239
+ android:orientation="vertical"
240
+ android:padding="16dp">
241
+
242
+ <TextView
243
+ style="@style/TextAppearance.MaterialComponents.Headline6"
244
+ android:layout_width="wrap_content"
245
+ android:layout_height="wrap_content"
246
+ android:layout_marginBottom="8dp"
247
+ android:text="@string/developer_tools_crash_testing" />
248
+
249
+ <com.google.android.material.button.MaterialButton
250
+ android:id="@+id/throwTestExceptionButton"
251
+ style="@style/Widget.MaterialComponents.Button.OutlinedButton"
252
+ android:layout_width="match_parent"
253
+ android:layout_height="wrap_content"
254
+ android:text="@string/developer_tools_throw_test_exception" />
255
+ </LinearLayout>
256
+ </com.google.android.material.card.MaterialCardView>
257
+
258
+ <com.google.android.material.card.MaterialCardView
259
+ android:layout_width="match_parent"
260
+ android:layout_height="wrap_content"
261
+ app:cardCornerRadius="12dp"
262
+ app:cardElevation="1dp">
263
+
264
+ <LinearLayout
265
+ android:layout_width="match_parent"
266
+ android:layout_height="wrap_content"
267
+ android:orientation="vertical"
268
+ android:padding="16dp">
269
+
270
+ <TextView
271
+ style="@style/TextAppearance.MaterialComponents.Headline6"
272
+ android:layout_width="wrap_content"
273
+ android:layout_height="wrap_content"
274
+ android:layout_marginBottom="12dp"
275
+ android:text="@string/developer_tools_webview" />
276
+
277
+ <LinearLayout
278
+ android:layout_width="match_parent"
279
+ android:layout_height="wrap_content"
280
+ android:layout_marginBottom="12dp"
281
+ android:gravity="center_vertical"
282
+ android:orientation="horizontal">
283
+
284
+ <TextView
285
+ style="@style/TextAppearance.MaterialComponents.Body1"
286
+ android:layout_width="0dp"
287
+ android:layout_height="wrap_content"
288
+ android:layout_weight="1"
289
+ android:text="@string/developer_tools_current_status" />
290
+
291
+ <TextView
292
+ android:id="@+id/webViewStatus"
293
+ style="@style/TextAppearance.MaterialComponents.Body2"
294
+ android:layout_width="wrap_content"
295
+ android:layout_height="wrap_content" />
296
+ </LinearLayout>
297
+
298
+ <com.google.android.material.button.MaterialButton
299
+ android:id="@+id/openChromeInspectDocsButton"
300
+ style="@style/Widget.MaterialComponents.Button.OutlinedButton"
301
+ android:layout_width="match_parent"
302
+ android:layout_height="wrap_content"
303
+ android:text="@string/developer_tools_open_chrome_inspect_docs" />
304
+ </LinearLayout>
305
+ </com.google.android.material.card.MaterialCardView>
306
+ </LinearLayout>
307
+ </androidx.core.widget.NestedScrollView>
308
+ </LinearLayout>
@@ -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"
@@ -18,4 +18,13 @@
18
18
  android:layout_gravity="center"
19
19
  android:indeterminateDrawable="@drawable/custom_loader" />
20
20
 
21
- </FrameLayout>
21
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
22
+ android:id="@+id/developerToolsFab"
23
+ android:layout_width="wrap_content"
24
+ android:layout_height="wrap_content"
25
+ android:layout_gravity="bottom|end"
26
+ android:layout_margin="24dp"
27
+ android:contentDescription="@string/developer_tools"
28
+ android:src="@android:drawable/ic_menu_info_details" />
29
+
30
+ </com.pakstr.app.debug.SupportUnlockGestureLayout>
@@ -3,4 +3,5 @@
3
3
 
4
4
  <dimen name="loader_height">50dp</dimen>
5
5
  <dimen name="loader_width">50dp</dimen>
6
+ <dimen name="developer_tools_dialog_padding">24dp</dimen>
6
7
  </resources>
@@ -1,3 +1,47 @@
1
1
  <resources>
2
2
  <string name="app_name">Pakstr</string>
3
+
4
+ <string name="developer_tools_title">Developer Tools</string>
5
+ <string name="developer_tools_runtime">Runtime</string>
6
+ <string name="developer_tools_session_id">Session ID</string>
7
+ <string name="developer_tools_app_version">App version</string>
8
+ <string name="developer_tools_android_version">Android version</string>
9
+ <string name="developer_tools_device_model">Device model</string>
10
+ <string name="developer_tools_app_uptime">App uptime</string>
11
+ <string name="developer_tools_version_value">%1$s (%2$d)</string>
12
+ <string name="developer_tools_device_value">%1$s %2$s</string>
13
+
14
+ <string name="developer_tools_debug">Debug</string>
15
+ <string name="developer_tools_enable_debug_mode">Enable Debug Mode</string>
16
+ <string name="developer_tools_enable_api">Enable API</string>
17
+ <string name="developer_tools_api_read_only">Controlled by the packaged runtime configuration.</string>
18
+ <string name="developer_tools_webview_debug">WebView Debug</string>
19
+ <string name="developer_tools_enabled">Enabled</string>
20
+ <string name="developer_tools_disabled">Disabled</string>
21
+
22
+ <string name="developer_tools_logs">Logs</string>
23
+ <string name="developer_tools_view_logs">View Logs</string>
24
+ <string name="developer_tools_clear_logs">Clear Logs</string>
25
+ <string name="developer_tools_export_debug_report">Export Debug Report</string>
26
+ <string name="developer_tools_share_report">Share Report</string>
27
+ <string name="developer_tools_no_logs">No logs recorded.</string>
28
+ <string name="developer_tools_clear_logs_confirmation">Clear all stored debug logs?</string>
29
+ <string name="developer_tools_clear">Clear</string>
30
+ <string name="developer_tools_logs_cleared">Logs cleared.</string>
31
+ <string name="developer_tools_report_exported">Report exported to %1$s</string>
32
+ <string name="developer_tools_report_export_failed">Unable to export the debug report.</string>
33
+
34
+ <string name="developer_tools_crash_testing">Crash Testing</string>
35
+ <string name="developer_tools_throw_test_exception">Throw Test Exception</string>
36
+ <string name="developer_tools_crash_confirmation_title">Crash Pakstr?</string>
37
+ <string name="developer_tools_crash_confirmation_message">This intentionally throws an uncaught RuntimeException and closes the app so CrashHandler can capture it.</string>
38
+ <string name="developer_tools_crash">Crash</string>
39
+
40
+ <string name="developer_tools_webview">WebView</string>
41
+ <string name="developer_tools_current_status">Current WebView Debug status</string>
42
+ <string name="developer_tools_open_chrome_inspect_docs">Open Chrome Inspect documentation</string>
43
+ <string name="developer_tools_browser_unavailable">No browser is available.</string>
44
+
45
+ <string name="developer_tools">Open Developer Tools</string>
46
+ <string name="diagnostics_mode_enabled">Diagnostics mode enabled</string>
3
47
  </resources>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.5.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",