pakstr 0.4.0 → 0.6.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.
Files changed (29) hide show
  1. package/android-template/app/build.gradle.kts +71 -4
  2. package/android-template/app/proguard-rules.pro +21 -18
  3. package/android-template/app/src/debug/AndroidManifest.xml +11 -0
  4. package/android-template/app/src/main/AndroidManifest.xml +6 -0
  5. package/android-template/app/src/main/java/com/pakstr/app/ApplicationClass.kt +14 -0
  6. package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +55 -5
  7. package/android-template/app/src/main/java/com/pakstr/app/ShellRuntime.kt +1 -1
  8. package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +135 -0
  9. package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +78 -5
  10. package/android-template/app/src/main/java/com/pakstr/app/debug/CrashHandler.kt +32 -0
  11. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugConfigProvider.kt +8 -3
  12. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugLogStorage.kt +43 -11
  13. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugPreferences.kt +85 -0
  14. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugReportExporter.kt +42 -32
  15. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugRuntimeOverride.kt +30 -4
  16. package/android-template/app/src/main/java/com/pakstr/app/debug/DebugSession.kt +16 -0
  17. package/android-template/app/src/main/java/com/pakstr/app/debug/DeveloperToolsActivity.kt +234 -0
  18. package/android-template/app/src/main/java/com/pakstr/app/debug/DeveloperToolsManager.kt +42 -13
  19. package/android-template/app/src/main/java/com/pakstr/app/debug/WebViewDebugManager.kt +30 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/debug/logging/PakstrLog.kt +16 -0
  21. package/android-template/app/src/main/java/com/pakstr/app/debug/logging/PakstrLogger.kt +154 -0
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +1 -1
  23. package/android-template/app/src/main/res/layout/activity_developer_tools.xml +308 -0
  24. package/android-template/app/src/main/res/layout/activity_layout.xml +9 -0
  25. package/android-template/app/src/main/res/values/dimes.xml +1 -0
  26. package/android-template/app/src/main/res/values/strings.xml +43 -0
  27. package/android-template/gradle/libs.versions.toml +1 -0
  28. package/android-template/gradle.properties +2 -1
  29. package/package.json +1 -1
@@ -11,6 +11,8 @@ object DebugLogStorage {
11
11
  private const val MAX_FILE_SIZE =
12
12
  1024 * 1024 // 1 MB
13
13
 
14
+ private const val MAX_LINES = 5000
15
+
14
16
  fun append(
15
17
  context: Context,
16
18
  entry: String
@@ -26,9 +28,13 @@ object DebugLogStorage {
26
28
 
27
29
 
28
30
  file.appendText(
31
+
29
32
  entry + "\n"
33
+
30
34
  )
31
35
 
36
+ trimFile(file)
37
+
32
38
  } catch (_: Exception) {
33
39
 
34
40
  // Logging must never crash the app
@@ -38,27 +44,21 @@ object DebugLogStorage {
38
44
 
39
45
  fun read(
40
46
  context: Context
41
- ): String {
47
+ ): List<String> {
42
48
 
43
49
  return try {
44
50
 
45
- val file =
46
- getFile(context)
47
-
51
+ val file = getFile(context)
48
52
 
49
53
  if (file.exists()) {
50
-
51
- file.readText()
52
-
54
+ file.readLines()
53
55
  } else {
54
-
55
- ""
56
-
56
+ emptyList()
57
57
  }
58
58
 
59
59
  } catch (_: Exception) {
60
60
 
61
- ""
61
+ emptyList()
62
62
 
63
63
  }
64
64
  }
@@ -104,4 +104,36 @@ object DebugLogStorage {
104
104
  )
105
105
 
106
106
  }
107
+
108
+ private fun trimFile(
109
+
110
+ file: File
111
+
112
+ ) {
113
+
114
+ try {
115
+
116
+ val lines =
117
+
118
+ file.readLines()
119
+
120
+ if (lines.size > MAX_LINES) {
121
+
122
+ file.writeText(
123
+
124
+ lines
125
+
126
+ .takeLast(MAX_LINES)
127
+
128
+ .joinToString("\n") + "\n"
129
+
130
+ )
131
+
132
+ }
133
+
134
+ } catch (_: Exception) {
135
+
136
+ }
137
+
138
+ }
107
139
  }
@@ -0,0 +1,85 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.content.Context
4
+
5
+ object DebugPreferences {
6
+
7
+ private const val PREFS_NAME = "pakstr_debug"
8
+
9
+ private const val KEY_ENABLED = "enabled"
10
+ private const val KEY_NETWORK = "networkLogs"
11
+ private const val KEY_WEBVIEW = "webViewLogs"
12
+
13
+
14
+ private fun prefs(context: Context) =
15
+ context.getSharedPreferences(
16
+ PREFS_NAME,
17
+ Context.MODE_PRIVATE
18
+ )
19
+
20
+
21
+ fun save(
22
+ context: Context,
23
+ config: DebugConfig
24
+ ) {
25
+
26
+ prefs(context)
27
+ .edit()
28
+ .putBoolean(
29
+ KEY_ENABLED,
30
+ config.enabled
31
+ )
32
+ .putBoolean(
33
+ KEY_NETWORK,
34
+ config.networkLogs
35
+ )
36
+ .putBoolean(
37
+ KEY_WEBVIEW,
38
+ config.webViewLogs
39
+ )
40
+ .apply()
41
+
42
+ }
43
+
44
+
45
+ fun load(
46
+ context: Context
47
+ ): DebugConfig {
48
+
49
+ val prefs = prefs(context)
50
+
51
+ return DebugConfig(
52
+
53
+ enabled =
54
+ prefs.getBoolean(
55
+ KEY_ENABLED,
56
+ false
57
+ ),
58
+
59
+ networkLogs =
60
+ prefs.getBoolean(
61
+ KEY_NETWORK,
62
+ false
63
+ ),
64
+
65
+ webViewLogs =
66
+ prefs.getBoolean(
67
+ KEY_WEBVIEW,
68
+ false
69
+ )
70
+
71
+ )
72
+ }
73
+
74
+
75
+ fun clear(
76
+ context: Context
77
+ ) {
78
+
79
+ prefs(context)
80
+ .edit()
81
+ .clear()
82
+ .apply()
83
+
84
+ }
85
+ }
@@ -38,7 +38,7 @@ object DebugReportExporter {
38
38
  val file =
39
39
  File(
40
40
  context.cacheDir,
41
- "pakstr-debug-report.txt"
41
+ "pakstr-debug-report-${DebugSession.sessionId}.txt"
42
42
  )
43
43
 
44
44
 
@@ -57,19 +57,8 @@ object DebugReportExporter {
57
57
 
58
58
 
59
59
  val rawLogs =
60
- if (debugEnabled) {
61
-
62
- DebugLogStorage.read(context)
63
-
64
- } else {
65
-
66
- DebugLogStorage.read(context)
67
- .lines()
68
- .filter {
69
- it.contains("[ERROR]")
70
- }
71
- .joinToString("\n")
72
- }
60
+ DebugLogStorage.read(context)
61
+ .joinToString("\n")
73
62
 
74
63
 
75
64
  val logs =
@@ -77,32 +66,53 @@ object DebugReportExporter {
77
66
 
78
67
 
79
68
  return """
80
- Pakstr Debug Report
81
- ===================
69
+ Pakstr Debug Report
70
+ ===================
71
+
72
+ Session:
73
+ -------------------
74
+ ID:
75
+ ${DebugSession.sessionId}
76
+
77
+ Started:
78
+ ${java.util.Date(DebugSession.startedAt)}
79
+
80
+ Uptime:
81
+ ${DebugSession.uptime()} ms
82
+
83
+
84
+ Application:
85
+ -------------------
86
+ ${BuildConfig.APPLICATION_ID}
87
+
88
+
89
+ Version:
90
+ ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})
91
+
92
+
93
+ Device:
94
+ ${Build.MANUFACTURER} ${Build.MODEL}
95
+
82
96
 
83
- Application:
84
- ${BuildConfig.APPLICATION_ID}
97
+ Android:
98
+ ${Build.VERSION.RELEASE}
85
99
 
86
- Version:
87
- ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})
88
100
 
89
- Device:
90
- ${Build.MANUFACTURER} ${Build.MODEL}
101
+ Debug:
102
+ $debugEnabled
91
103
 
92
- Android:
93
- ${Build.VERSION.RELEASE}
94
104
 
95
- Debug:
96
- $debugEnabled
105
+ API Enabled:
106
+ ${RuntimeConfig.apiEnabled(context)}
97
107
 
98
- API Enabled:
99
- ${RuntimeConfig.apiEnabled(context)}
108
+ WebView Debug:
109
+ ${BuildConfig.DEBUG || RuntimeConfig.debugEnabled(context)}
100
110
 
101
- Logs:
102
- -------------------
103
- $logs
111
+ Logs:
112
+ -------------------
113
+ $logs
104
114
 
105
- """.trimIndent()
115
+ """.trimIndent()
106
116
  }
107
117
 
108
118
 
@@ -1,29 +1,55 @@
1
1
  package com.pakstr.app.debug
2
2
 
3
+ import android.content.Context
4
+
5
+
3
6
  object DebugRuntimeOverride {
4
7
 
5
- private var overrideConfig: DebugConfig? = null
8
+
9
+ private var overrideConfig:
10
+ DebugConfig? = null
6
11
 
7
12
 
8
13
  fun set(
14
+ context: Context,
9
15
  config: DebugConfig
10
16
  ) {
11
17
 
12
18
  overrideConfig = config
13
19
 
20
+ DebugPreferences.save(
21
+ context,
22
+ config
23
+ )
14
24
  }
15
25
 
16
26
 
17
- fun clear() {
27
+ fun clear(
28
+ context: Context
29
+ ) {
18
30
 
19
31
  overrideConfig = null
20
32
 
33
+ DebugPreferences.clear(
34
+ context
35
+ )
21
36
  }
22
37
 
23
38
 
24
- fun get(): DebugConfig? {
39
+ fun get(
40
+ context: Context
41
+ ): DebugConfig? {
25
42
 
26
- return overrideConfig
43
+ if (overrideConfig == null) {
44
+
45
+ val saved =
46
+ DebugPreferences.load(context)
27
47
 
48
+ if (saved.enabled) {
49
+ overrideConfig = saved
50
+ }
51
+ }
52
+
53
+ return overrideConfig
28
54
  }
29
55
  }
@@ -0,0 +1,16 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import java.util.UUID
4
+
5
+ object DebugSession {
6
+
7
+ val sessionId: String =
8
+ UUID.randomUUID().toString()
9
+
10
+ val startedAt: Long =
11
+ System.currentTimeMillis()
12
+
13
+ fun uptime(): Long {
14
+ return System.currentTimeMillis() - startedAt
15
+ }
16
+ }
@@ -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
+ }
@@ -1,34 +1,63 @@
1
1
  package com.pakstr.app.debug
2
+
3
+ import android.content.Context
4
+
5
+
2
6
  object DeveloperToolsManager {
3
7
 
4
- fun enableDebug() {
5
8
 
6
- DebugRuntimeOverride.set(
9
+ fun enableDebug(
10
+ context: Context
11
+ ) {
7
12
 
8
- DebugConfig(
13
+ val config = DebugConfig(
9
14
 
10
- enabled = true,
15
+ enabled = true,
11
16
 
12
- networkLogs = true,
17
+ networkLogs = true,
13
18
 
14
- webViewLogs = true,
19
+ webViewLogs = true
20
+
21
+ )
22
+
23
+
24
+
25
+ DebugRuntimeOverride.set(context,
26
+ config
27
+ )
15
28
 
16
- )
29
+ DebugPreferences.save(
30
+ context,
31
+ config
17
32
  )
18
33
 
19
34
  }
20
35
 
21
- fun disableDebug() {
22
36
 
23
- DebugRuntimeOverride.clear()
37
+ fun disableDebug(
38
+ context: Context
39
+ ) {
40
+
41
+
42
+ DebugRuntimeOverride.clear(context)
43
+
44
+
45
+ DebugPreferences.clear(
46
+ context
47
+ )
24
48
 
25
49
  }
26
50
 
27
- fun isEnabled(): Boolean {
28
51
 
29
- return DebugRuntimeOverride.get()
30
- ?.enabled
31
- ?: false
52
+ fun isEnabled(
53
+ context: Context
54
+ ): Boolean {
55
+
56
+
57
+ return DebugPreferences
58
+ .load(context)
59
+ .enabled
32
60
 
33
61
  }
62
+
34
63
  }
@@ -0,0 +1,30 @@
1
+ package com.pakstr.app.debug
2
+
3
+ import android.R.attr.enabled
4
+ import android.content.Context
5
+ import android.webkit.WebView
6
+ import com.pakstr.app.BuildConfig
7
+ import com.pakstr.app.RuntimeConfig
8
+
9
+ object WebViewDebugManager {
10
+
11
+
12
+ fun enable(
13
+ context: Context
14
+ ) {
15
+
16
+ WebView.setWebContentsDebuggingEnabled(
17
+ BuildConfig.DEBUG ||
18
+ RuntimeConfig.debugEnabled(context)
19
+ )
20
+ AppDebugLogger.log(
21
+
22
+ context,
23
+
24
+ "WEBVIEW",
25
+
26
+ "WebView debugging enabled: $enabled"
27
+
28
+ )
29
+ }
30
+ }