From b58002b7a9da034e5ec9c7ef4cc7d6e9ddd87e32 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 14:38:02 +0300 Subject: [PATCH 01/13] add basics to android --- .../margelo/nitro/toadly/LoggingService.kt | 81 +++++++++++++++++++ .../java/com/margelo/nitro/toadly/Toadly.kt | 41 +++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt diff --git a/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt new file mode 100644 index 0000000..a5c51dc --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt @@ -0,0 +1,81 @@ +package com.margelo.nitro.toadly + +import android.util.Log +import java.text.SimpleDateFormat +import java.util.* + +/** + * LoggingService manages log collection and provides logging utilities + */ +class LoggingService { + companion object { + private const val TAG = "Toadly" + private const val MAX_LOGS = 50 + private val logs = mutableListOf() + private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + /** + * Log a message with the specified level + */ + private fun log(message: String, level: String = "INFO") { + val timestamp = dateFormat.format(Date()) + val logEntry = "[$timestamp] [$level] $message" + + synchronized(logs) { + logs.add(logEntry) + + if (logs.size > MAX_LOGS) { + logs.removeAt(0) + } + } + + when (level) { + "INFO" -> Log.i(TAG, message) + "WARN" -> Log.w(TAG, message) + "ERROR" -> Log.e(TAG, message) + else -> Log.d(TAG, message) + } + } + + /** + * Log an info message + */ + fun info(message: String) { + log(message, "INFO") + } + + /** + * Log a warning message + */ + fun warn(message: String) { + log(message, "WARN") + } + + /** + * Log an error message + */ + fun error(message: String) { + log(message, "ERROR") + } + + /** + * Get all recent logs as a single string + */ + fun getRecentLogs(): String { + synchronized(logs) { + return logs.joinToString("\n") + } + } + + /** + * Clear all stored logs + */ + fun clearLogs() { + synchronized(logs) { + logs.clear() + } + } + } +} diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index 4267827..8908e82 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -1,10 +1,47 @@ package com.margelo.nitro.toadly import com.facebook.proguard.annotations.DoNotStrip +import android.os.Handler +import android.os.Looper @DoNotStrip class Toadly : HybridToadlySpec() { - override fun multiply(a: Double, b: Double): Double { - return a * b + private var hasSetupBeenCalled = false + private var jsLogs: String = "" + + + override fun setup(githubToken: String, repoOwner: String, repoName: String) { + if (hasSetupBeenCalled) { + return + } + hasSetupBeenCalled = true + + LoggingService.info("Setting up Toadly (GitHub integration skipped for now)") + // GitHub integration will be implemented later + } + + override fun addJSLogs(logs: String) { + this.jsLogs = logs + LoggingService.info("Received JavaScript logs") + } + + override fun createIssueWithTitle(title: String, reportType: String?) { + LoggingService.info("Creating issue with title: $title (GitHub integration skipped for now)") + + // GitHub integration will be implemented later + // For now, just log the request + Handler(Looper.getMainLooper()).post { + LoggingService.info("Would create issue: $title, type: ${reportType ?: "bug"}") + } + } + + override fun show() { + LoggingService.info("Show bug report dialog requested (not implemented yet)") + // Bug report dialog will be implemented later + } + + override fun crashNative() { + LoggingService.warn("Crash native requested - this is for testing only") + throw RuntimeException("Manually triggered crash from Toadly") } } From 029d1eac768f082297be3d39338610625672f70e Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:00:48 +0300 Subject: [PATCH 02/13] basic dialog android --- .../com/margelo/nitro/toadly/ReportType.kt | 15 ++ .../java/com/margelo/nitro/toadly/Toadly.kt | 165 ++++++++++++++---- .../src/main/res/layout/dialog_bug_report.xml | 38 ++++ android/src/main/res/values/strings.xml | 10 ++ 4 files changed, 197 insertions(+), 31 deletions(-) create mode 100644 android/src/main/java/com/margelo/nitro/toadly/ReportType.kt create mode 100644 android/src/main/res/layout/dialog_bug_report.xml create mode 100644 android/src/main/res/values/strings.xml diff --git a/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt b/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt new file mode 100644 index 0000000..1ad2c5c --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt @@ -0,0 +1,15 @@ +package com.margelo.nitro.toadly + +enum class ReportType(val displayName: String, val icon: String) { + BUG("Bug", "🐞"), + SUGGESTION("Suggestion", "💡"), + QUESTION("Question", "❓"), + CRASH("Crash", "🚨"); + + val displayText: String + get() = "$icon $displayName" + + companion object { + fun getDefault(): ReportType = BUG + } +} diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index 8908e82..34ab642 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -1,47 +1,150 @@ package com.margelo.nitro.toadly - + import com.facebook.proguard.annotations.DoNotStrip import android.os.Handler import android.os.Looper +import android.app.AlertDialog +import android.content.Context +import android.view.LayoutInflater +import android.widget.EditText +import android.widget.Spinner +import android.widget.ArrayAdapter +import android.widget.Toast +import kotlin.collections.Map @DoNotStrip class Toadly : HybridToadlySpec() { - private var hasSetupBeenCalled = false - private var jsLogs: String = "" + private var hasSetupBeenCalled = false + private var jsLogs = "" + private var githubToken = "" + private var repoOwner = "" + private var repoName = "" + override fun setup(githubToken: String, repoOwner: String, repoName: String) { + if (hasSetupBeenCalled) { + return + } + hasSetupBeenCalled = true + + this.githubToken = githubToken + this.repoOwner = repoOwner + this.repoName = repoName + + LoggingService.info("Setting up Toadly with GitHub integration") + } - override fun setup(githubToken: String, repoOwner: String, repoName: String) { - if (hasSetupBeenCalled) { - return + override fun addJSLogs(logs: String) { + this.jsLogs = logs + LoggingService.info("Received JavaScript logs") } - hasSetupBeenCalled = true - LoggingService.info("Setting up Toadly (GitHub integration skipped for now)") - // GitHub integration will be implemented later - } + override fun createIssueWithTitle(title: String, reportType: String?) { + LoggingService.info("Creating issue with title: $title, type: ${reportType ?: "bug"}") + // GitHub integration will be implemented later + } - override fun addJSLogs(logs: String) { - this.jsLogs = logs - LoggingService.info("Received JavaScript logs") - } + override fun show() { + LoggingService.info("Show bug report dialog requested") + + // Get the current activity + val currentActivity = getCurrentActivity() + if (currentActivity == null) { + LoggingService.error("Cannot show bug report dialog: no activity found") + return + } + + // Show the bug report dialog on the UI thread + Handler(Looper.getMainLooper()).post { + try { + // Create a simple dialog with EditText fields + val layout = LayoutInflater.from(currentActivity).inflate(R.layout.dialog_bug_report, null) + + // Get references to the views + val emailEditText = layout.findViewById(R.id.emailEditText) + val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) + val descriptionEditText = layout.findViewById(R.id.descriptionEditText) + + // Set up the report type spinner + val reportTypes = arrayOf("Bug 🐞", "Suggestion 💡", "Question ❓", "Crash 🚨") + val adapter = ArrayAdapter(currentActivity, android.R.layout.simple_spinner_item, reportTypes) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + reportTypeSpinner.adapter = adapter + + // Create and show the dialog + val dialog = AlertDialog.Builder(currentActivity) + .setTitle(R.string.bug_report_title) + .setView(layout) + .setPositiveButton(R.string.bug_report_submit) { _, _ -> + val email = emailEditText.text?.toString() ?: "" + val description = descriptionEditText.text?.toString() ?: "" + val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] + + if (email.isEmpty() || description.isEmpty()) { + Toast.makeText(currentActivity, "Please fill all fields", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + // Generate a title from the description + val title = if (description.length > 50) { + description.substring(0, 47) + "..." + } else { + description + } + + // Submit the issue + createIssueWithTitle(title, reportType.split(" ")[0].lowercase()) + Toast.makeText(currentActivity, "Bug report submitted", Toast.LENGTH_SHORT).show() + } + .setNegativeButton(R.string.bug_report_cancel, null) + .create() + + dialog.show() + LoggingService.info("Bug report dialog shown") + } catch (e: Exception) { + LoggingService.error("Error showing bug report dialog: ${e.message}") + } + } + } - override fun createIssueWithTitle(title: String, reportType: String?) { - LoggingService.info("Creating issue with title: $title (GitHub integration skipped for now)") + override fun crashNative() { + LoggingService.warn("Crash native requested - this is for testing only") + throw RuntimeException("Manually triggered crash from Toadly") + } - // GitHub integration will be implemented later - // For now, just log the request - Handler(Looper.getMainLooper()).post { - LoggingService.info("Would create issue: $title, type: ${reportType ?: "bug"}") + // Helper method to get the current activity + private fun getCurrentActivity(): Context? { + try { + val activityThreadClass = Class.forName("android.app.ActivityThread") + val activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null) + val activityField = activityThreadClass.getDeclaredField("mActivities") + activityField.isAccessible = true + + @Suppress("UNCHECKED_CAST") + val activities = activityField.get(activityThread) as? Map + if (activities == null) { + return null + } + + activities.values.forEach { activityRecord -> + activityRecord?.let { record -> + val activityRecordClass = record::class.java + val pausedField = activityRecordClass.getDeclaredField("paused") + pausedField.isAccessible = true + if (!pausedField.getBoolean(record)) { + val activityField = activityRecordClass.getDeclaredField("activity") + activityField.isAccessible = true + val activity = activityField.get(record) + if (activity is Context) { + return activity + } + } + } + } + + return null + } catch (e: Exception) { + LoggingService.error("Error getting current activity: ${e.message}") + return null + } } - } - - override fun show() { - LoggingService.info("Show bug report dialog requested (not implemented yet)") - // Bug report dialog will be implemented later - } - - override fun crashNative() { - LoggingService.warn("Crash native requested - this is for testing only") - throw RuntimeException("Manually triggered crash from Toadly") - } } diff --git a/android/src/main/res/layout/dialog_bug_report.xml b/android/src/main/res/layout/dialog_bug_report.xml new file mode 100644 index 0000000..00c2aa3 --- /dev/null +++ b/android/src/main/res/layout/dialog_bug_report.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..5b678f5 --- /dev/null +++ b/android/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + Toadly + Need help? + Email + Description + Report Type + Submit + Cancel + From 29047398f9b442450b0fc55da571aad7ea7d0a5a Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:02:51 +0300 Subject: [PATCH 03/13] use list --- android/src/main/java/com/margelo/nitro/toadly/Toadly.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index 34ab642..42891e6 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -15,7 +15,7 @@ import kotlin.collections.Map @DoNotStrip class Toadly : HybridToadlySpec() { private var hasSetupBeenCalled = false - private var jsLogs = "" + private var jsLogs = mutableListOf() private var githubToken = "" private var repoOwner = "" private var repoName = "" @@ -34,7 +34,7 @@ class Toadly : HybridToadlySpec() { } override fun addJSLogs(logs: String) { - this.jsLogs = logs + this.jsLogs.add(logs) LoggingService.info("Received JavaScript logs") } From 0684dd2ad25bc9b74bd3789965d75b1bef7a4be7 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:13:35 +0300 Subject: [PATCH 04/13] cleanup --- .../java/com/margelo/nitro/toadly/Toadly.kt | 86 ++++--------------- .../nitro/toadly/dialog/BugReportDialog.kt | 63 ++++++++++++++ 2 files changed, 81 insertions(+), 68 deletions(-) create mode 100644 android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index 42891e6..c191ec8 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -1,16 +1,9 @@ package com.margelo.nitro.toadly import com.facebook.proguard.annotations.DoNotStrip -import android.os.Handler -import android.os.Looper -import android.app.AlertDialog import android.content.Context -import android.view.LayoutInflater -import android.widget.EditText -import android.widget.Spinner -import android.widget.ArrayAdapter -import android.widget.Toast import kotlin.collections.Map +import com.margelo.nitro.toadly.dialog.BugReportDialog @DoNotStrip class Toadly : HybridToadlySpec() { @@ -25,11 +18,11 @@ class Toadly : HybridToadlySpec() { return } hasSetupBeenCalled = true - + this.githubToken = githubToken this.repoOwner = repoOwner this.repoName = repoName - + LoggingService.info("Setting up Toadly with GitHub integration") } @@ -45,102 +38,59 @@ class Toadly : HybridToadlySpec() { override fun show() { LoggingService.info("Show bug report dialog requested") - - // Get the current activity + val currentActivity = getCurrentActivity() + if (currentActivity == null) { LoggingService.error("Cannot show bug report dialog: no activity found") return } - - // Show the bug report dialog on the UI thread - Handler(Looper.getMainLooper()).post { - try { - // Create a simple dialog with EditText fields - val layout = LayoutInflater.from(currentActivity).inflate(R.layout.dialog_bug_report, null) - - // Get references to the views - val emailEditText = layout.findViewById(R.id.emailEditText) - val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) - val descriptionEditText = layout.findViewById(R.id.descriptionEditText) - - // Set up the report type spinner - val reportTypes = arrayOf("Bug 🐞", "Suggestion 💡", "Question ❓", "Crash 🚨") - val adapter = ArrayAdapter(currentActivity, android.R.layout.simple_spinner_item, reportTypes) - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - reportTypeSpinner.adapter = adapter - - // Create and show the dialog - val dialog = AlertDialog.Builder(currentActivity) - .setTitle(R.string.bug_report_title) - .setView(layout) - .setPositiveButton(R.string.bug_report_submit) { _, _ -> - val email = emailEditText.text?.toString() ?: "" - val description = descriptionEditText.text?.toString() ?: "" - val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] - - if (email.isEmpty() || description.isEmpty()) { - Toast.makeText(currentActivity, "Please fill all fields", Toast.LENGTH_SHORT).show() - return@setPositiveButton - } - - // Generate a title from the description - val title = if (description.length > 50) { - description.substring(0, 47) + "..." - } else { - description - } - - // Submit the issue - createIssueWithTitle(title, reportType.split(" ")[0].lowercase()) - Toast.makeText(currentActivity, "Bug report submitted", Toast.LENGTH_SHORT).show() - } - .setNegativeButton(R.string.bug_report_cancel, null) - .create() - - dialog.show() - LoggingService.info("Bug report dialog shown") - } catch (e: Exception) { - LoggingService.error("Error showing bug report dialog: ${e.message}") - } - } + + BugReportDialog(currentActivity) { title, reportType -> + createIssueWithTitle(title, reportType) + }.show() } override fun crashNative() { LoggingService.warn("Crash native requested - this is for testing only") throw RuntimeException("Manually triggered crash from Toadly") } - + // Helper method to get the current activity private fun getCurrentActivity(): Context? { try { val activityThreadClass = Class.forName("android.app.ActivityThread") val activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null) val activityField = activityThreadClass.getDeclaredField("mActivities") + activityField.isAccessible = true - + @Suppress("UNCHECKED_CAST") val activities = activityField.get(activityThread) as? Map + if (activities == null) { return null } - + activities.values.forEach { activityRecord -> activityRecord?.let { record -> val activityRecordClass = record::class.java val pausedField = activityRecordClass.getDeclaredField("paused") + pausedField.isAccessible = true + if (!pausedField.getBoolean(record)) { val activityField = activityRecordClass.getDeclaredField("activity") activityField.isAccessible = true val activity = activityField.get(record) + if (activity is Context) { return activity } } } } - + return null } catch (e: Exception) { LoggingService.error("Error getting current activity: ${e.message}") diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt new file mode 100644 index 0000000..c0fa3ed --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -0,0 +1,63 @@ +package com.margelo.nitro.toadly.dialog + +import android.app.AlertDialog +import android.content.Context +import android.view.LayoutInflater +import android.widget.EditText +import android.widget.Spinner +import android.widget.ArrayAdapter +import android.widget.Toast +import android.os.Handler +import android.os.Looper +import com.margelo.nitro.toadly.LoggingService +import com.margelo.nitro.toadly.R + +class BugReportDialog(private val context: Context, private val onSubmit: (String, String) -> Unit) { + private val reportTypes = arrayOf("Bug 🐞", "Suggestion 💡", "Question ❓") + + fun show() { + Handler(Looper.getMainLooper()).post { + try { + val layout = LayoutInflater.from(context).inflate(R.layout.dialog_bug_report, null) + + val emailEditText = layout.findViewById(R.id.emailEditText) + val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) + val descriptionEditText = layout.findViewById(R.id.descriptionEditText) + + val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, reportTypes) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + reportTypeSpinner.adapter = adapter + + val dialog = AlertDialog.Builder(context) + .setTitle(R.string.bug_report_title) + .setView(layout) + .setPositiveButton(R.string.bug_report_submit) { _, _ -> + val email = emailEditText.text?.toString() ?: "" + val description = descriptionEditText.text?.toString() ?: "" + val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] + + if (email.isEmpty() || description.isEmpty()) { + Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + val title = if (description.length > 50) { + description.substring(0, 47) + "..." + } else { + description + } + + onSubmit(title, reportType.split(" ")[0].lowercase()) + Toast.makeText(context, "Bug report submitted", Toast.LENGTH_SHORT).show() + } + .setNegativeButton(R.string.bug_report_cancel, null) + .create() + + dialog.show() + LoggingService.info("Bug report dialog shown") + } catch (e: Exception) { + LoggingService.error("Error showing bug report dialog: ${e.message}") + } + } + } +} From e741c8e4ea93dba820f0c835b1fdc41ecfb53510 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:24:58 +0300 Subject: [PATCH 05/13] iterate dialog --- android/build.gradle | 9 ++- .../nitro/toadly/dialog/BugReportDialog.kt | 66 +++++++++++-------- .../main/res/drawable/dialog_background.xml | 5 ++ .../res/drawable/edit_text_background.xml | 6 ++ android/src/main/res/drawable/ic_close.xml | 10 +++ android/src/main/res/drawable/ic_send.xml | 10 +++ .../src/main/res/layout/dialog_bug_report.xml | 61 ++++++++++++++--- android/src/main/res/values/styles.xml | 10 +++ 8 files changed, 135 insertions(+), 42 deletions(-) create mode 100644 android/src/main/res/drawable/dialog_background.xml create mode 100644 android/src/main/res/drawable/edit_text_background.xml create mode 100644 android/src/main/res/drawable/ic_close.xml create mode 100644 android/src/main/res/drawable/ic_send.xml create mode 100644 android/src/main/res/values/styles.xml diff --git a/android/build.gradle b/android/build.gradle index 73799ff..171c874 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -117,8 +117,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' } sourceSets { @@ -143,4 +147,3 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(":react-native-nitro-modules") } - diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index c0fa3ed..fafa18e 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -4,9 +4,10 @@ import android.app.AlertDialog import android.content.Context import android.view.LayoutInflater import android.widget.EditText -import android.widget.Spinner +import android.widget.AutoCompleteTextView import android.widget.ArrayAdapter import android.widget.Toast +import android.widget.ImageButton import android.os.Handler import android.os.Looper import com.margelo.nitro.toadly.LoggingService @@ -18,41 +19,48 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin fun show() { Handler(Looper.getMainLooper()).post { try { + val dialog = AlertDialog.Builder(context, R.style.CustomDialog) + .create() + val layout = LayoutInflater.from(context).inflate(R.layout.dialog_bug_report, null) val emailEditText = layout.findViewById(R.id.emailEditText) - val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) + val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) val descriptionEditText = layout.findViewById(R.id.descriptionEditText) + val closeButton = layout.findViewById(R.id.closeButton) + val sendButton = layout.findViewById(R.id.sendButton) - val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, reportTypes) - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - reportTypeSpinner.adapter = adapter - - val dialog = AlertDialog.Builder(context) - .setTitle(R.string.bug_report_title) - .setView(layout) - .setPositiveButton(R.string.bug_report_submit) { _, _ -> - val email = emailEditText.text?.toString() ?: "" - val description = descriptionEditText.text?.toString() ?: "" - val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] - - if (email.isEmpty() || description.isEmpty()) { - Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show() - return@setPositiveButton - } - - val title = if (description.length > 50) { - description.substring(0, 47) + "..." - } else { - description - } - - onSubmit(title, reportType.split(" ")[0].lowercase()) - Toast.makeText(context, "Bug report submitted", Toast.LENGTH_SHORT).show() + val adapter = ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, reportTypes) + reportTypeSpinner.setAdapter(adapter) + reportTypeSpinner.setText(reportTypes[0], false) + + closeButton.setOnClickListener { + dialog.dismiss() + } + + sendButton.setOnClickListener { + val email = emailEditText.text?.toString() ?: "" + val description = descriptionEditText.text?.toString() ?: "" + val reportType = reportTypeSpinner.text?.toString() ?: reportTypes[0] + + if (email.isEmpty() || description.isEmpty()) { + Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show() + return@setOnClickListener } - .setNegativeButton(R.string.bug_report_cancel, null) - .create() + val title = if (description.length > 50) { + description.substring(0, 47) + "..." + } else { + description + } + + onSubmit(title, reportType.split(" ")[0].lowercase()) + Toast.makeText(context, "Bug report submitted", Toast.LENGTH_SHORT).show() + dialog.dismiss() + } + + dialog.setView(layout) + dialog.window?.setBackgroundDrawableResource(android.R.color.transparent) dialog.show() LoggingService.info("Bug report dialog shown") } catch (e: Exception) { diff --git a/android/src/main/res/drawable/dialog_background.xml b/android/src/main/res/drawable/dialog_background.xml new file mode 100644 index 0000000..ca7d6bf --- /dev/null +++ b/android/src/main/res/drawable/dialog_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/src/main/res/drawable/edit_text_background.xml b/android/src/main/res/drawable/edit_text_background.xml new file mode 100644 index 0000000..d34c97f --- /dev/null +++ b/android/src/main/res/drawable/edit_text_background.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/src/main/res/drawable/ic_close.xml b/android/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..a5182e6 --- /dev/null +++ b/android/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + + diff --git a/android/src/main/res/drawable/ic_send.xml b/android/src/main/res/drawable/ic_send.xml new file mode 100644 index 0000000..565382b --- /dev/null +++ b/android/src/main/res/drawable/ic_send.xml @@ -0,0 +1,10 @@ + + + + diff --git a/android/src/main/res/layout/dialog_bug_report.xml b/android/src/main/res/layout/dialog_bug_report.xml index 00c2aa3..3256a8c 100644 --- a/android/src/main/res/layout/dialog_bug_report.xml +++ b/android/src/main/res/layout/dialog_bug_report.xml @@ -3,7 +3,45 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="16dp"> + android:background="@drawable/dialog_background" + android:padding="20dp"> + + + + + + + + + + + android:inputType="textEmailAddress" /> - + android:padding="16dp" + android:background="@drawable/edit_text_background" + android:hint="Report Type" + android:inputType="none" /> + android:inputType="textMultiLine" /> diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml new file mode 100644 index 0000000..1c7fc36 --- /dev/null +++ b/android/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + From 634e7bba66eb0e47f76e3db7ab33a6184f5dc89e Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:52:04 +0300 Subject: [PATCH 06/13] match ios styling for dialog --- .../nitro/toadly/dialog/BugReportDialog.kt | 13 +-- .../main/res/drawable/dialog_background.xml | 3 +- .../main/res/drawable/header_background.xml | 7 ++ .../src/main/res/layout/dialog_bug_report.xml | 83 ++++++++++--------- 4 files changed, 62 insertions(+), 44 deletions(-) create mode 100644 android/src/main/res/drawable/header_background.xml diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index fafa18e..2342140 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -4,12 +4,13 @@ import android.app.AlertDialog import android.content.Context import android.view.LayoutInflater import android.widget.EditText -import android.widget.AutoCompleteTextView +import android.widget.Spinner import android.widget.ArrayAdapter import android.widget.Toast import android.widget.ImageButton import android.os.Handler import android.os.Looper +import androidx.appcompat.widget.AppCompatSpinner import com.margelo.nitro.toadly.LoggingService import com.margelo.nitro.toadly.R @@ -25,14 +26,14 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin val layout = LayoutInflater.from(context).inflate(R.layout.dialog_bug_report, null) val emailEditText = layout.findViewById(R.id.emailEditText) - val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) + val reportTypeSpinner = layout.findViewById(R.id.reportTypeSpinner) val descriptionEditText = layout.findViewById(R.id.descriptionEditText) val closeButton = layout.findViewById(R.id.closeButton) val sendButton = layout.findViewById(R.id.sendButton) - val adapter = ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, reportTypes) - reportTypeSpinner.setAdapter(adapter) - reportTypeSpinner.setText(reportTypes[0], false) + val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, reportTypes) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + reportTypeSpinner.adapter = adapter closeButton.setOnClickListener { dialog.dismiss() @@ -41,7 +42,7 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin sendButton.setOnClickListener { val email = emailEditText.text?.toString() ?: "" val description = descriptionEditText.text?.toString() ?: "" - val reportType = reportTypeSpinner.text?.toString() ?: reportTypes[0] + val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] if (email.isEmpty() || description.isEmpty()) { Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show() diff --git a/android/src/main/res/drawable/dialog_background.xml b/android/src/main/res/drawable/dialog_background.xml index ca7d6bf..afbe190 100644 --- a/android/src/main/res/drawable/dialog_background.xml +++ b/android/src/main/res/drawable/dialog_background.xml @@ -1,5 +1,6 @@ - + diff --git a/android/src/main/res/drawable/header_background.xml b/android/src/main/res/drawable/header_background.xml new file mode 100644 index 0000000..7335a39 --- /dev/null +++ b/android/src/main/res/drawable/header_background.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/android/src/main/res/layout/dialog_bug_report.xml b/android/src/main/res/layout/dialog_bug_report.xml index 3256a8c..4b0cba9 100644 --- a/android/src/main/res/layout/dialog_bug_report.xml +++ b/android/src/main/res/layout/dialog_bug_report.xml @@ -3,77 +3,86 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="@drawable/dialog_background" - android:padding="20dp"> + android:background="@drawable/dialog_background"> + android:background="@drawable/header_background" + android:paddingVertical="16dp" + android:paddingHorizontal="8dp"> + android:layout_centerVertical="true" + android:layout_marginHorizontal="8dp" /> - - + + android:orientation="vertical" + android:padding="16dp"> - - + + - - + + + + + From 61f214933d24a8a9973d6efb1000f97410768651 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 15:55:23 +0300 Subject: [PATCH 07/13] match --- .../java/com/margelo/nitro/toadly/ReportType.kt | 15 --------------- .../nitro/toadly/dialog/BugReportDialog.kt | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 android/src/main/java/com/margelo/nitro/toadly/ReportType.kt diff --git a/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt b/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt deleted file mode 100644 index 1ad2c5c..0000000 --- a/android/src/main/java/com/margelo/nitro/toadly/ReportType.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.margelo.nitro.toadly - -enum class ReportType(val displayName: String, val icon: String) { - BUG("Bug", "🐞"), - SUGGESTION("Suggestion", "💡"), - QUESTION("Question", "❓"), - CRASH("Crash", "🚨"); - - val displayText: String - get() = "$icon $displayName" - - companion object { - fun getDefault(): ReportType = BUG - } -} diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index 2342140..85365d7 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -15,7 +15,7 @@ import com.margelo.nitro.toadly.LoggingService import com.margelo.nitro.toadly.R class BugReportDialog(private val context: Context, private val onSubmit: (String, String) -> Unit) { - private val reportTypes = arrayOf("Bug 🐞", "Suggestion 💡", "Question ❓") + private val reportTypes = arrayOf("🐞 Bug", "💡 Suggestion", "❓ Question") fun show() { Handler(Looper.getMainLooper()).post { From c78a04df3b9fc167ce1fc377932a9bb1f053172e Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 16:10:36 +0300 Subject: [PATCH 08/13] add report ability --- .../margelo/nitro/toadly/LoggingService.kt | 123 +++++++++--------- .../java/com/margelo/nitro/toadly/Toadly.kt | 28 +++- .../nitro/toadly/dialog/BugReportDialog.kt | 18 ++- .../nitro/toadly/github/GitHubService.kt | 75 +++++++++++ 4 files changed, 171 insertions(+), 73 deletions(-) create mode 100644 android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt diff --git a/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt index a5c51dc..b000f42 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt @@ -1,81 +1,80 @@ package com.margelo.nitro.toadly import android.util.Log +import android.R import java.text.SimpleDateFormat import java.util.* /** * LoggingService manages log collection and provides logging utilities */ -class LoggingService { - companion object { - private const val TAG = "Toadly" - private const val MAX_LOGS = 50 - private val logs = mutableListOf() - private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } +object LoggingService { + private const val TAG = "Toadly" + private const val MAX_LOGS = 50 + private val logs = mutableListOf() + private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } - /** - * Log a message with the specified level - */ - private fun log(message: String, level: String = "INFO") { - val timestamp = dateFormat.format(Date()) - val logEntry = "[$timestamp] [$level] $message" - - synchronized(logs) { - logs.add(logEntry) - - if (logs.size > MAX_LOGS) { - logs.removeAt(0) - } - } + /** + * Log a message with the specified level + */ + private fun log(message: String, level: String = "INFO") { + val timestamp = dateFormat.format(Date()) + val logEntry = "[$timestamp] [$level] $message" + + synchronized(logs) { + logs.add(logEntry) - when (level) { - "INFO" -> Log.i(TAG, message) - "WARN" -> Log.w(TAG, message) - "ERROR" -> Log.e(TAG, message) - else -> Log.d(TAG, message) + if (logs.size > MAX_LOGS) { + logs.removeAt(0) } } - /** - * Log an info message - */ - fun info(message: String) { - log(message, "INFO") - } - - /** - * Log a warning message - */ - fun warn(message: String) { - log(message, "WARN") - } - - /** - * Log an error message - */ - fun error(message: String) { - log(message, "ERROR") + when (level) { + "INFO" -> Log.i(TAG, message) + "WARN" -> Log.w(TAG, message) + "ERROR" -> Log.e(TAG, message) + else -> Log.d(TAG, message) } - - /** - * Get all recent logs as a single string - */ - fun getRecentLogs(): String { - synchronized(logs) { - return logs.joinToString("\n") - } + } + + /** + * Log an info message + */ + fun info(message: String) { + log(message, "INFO") + } + + /** + * Log a warning message + */ + fun warn(message: String) { + log(message, "WARN") + } + + /** + * Log an error message + */ + fun error(message: String) { + log(message, "ERROR") + } + + /** + * Get all recent logs as a single string + */ + fun getRecentLogs(): String { + synchronized(logs) { + return logs.joinToString("\n") } - - /** - * Clear all stored logs - */ - fun clearLogs() { - synchronized(logs) { - logs.clear() - } + } + + /** + * Clear all stored logs + */ + fun clearLogs() { + synchronized(logs) { + logs.clear() } } } diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index c191ec8..f767750 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -1,17 +1,21 @@ package com.margelo.nitro.toadly -import com.facebook.proguard.annotations.DoNotStrip +import android.app.Activity import android.content.Context -import kotlin.collections.Map +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.proguard.annotations.DoNotStrip import com.margelo.nitro.toadly.dialog.BugReportDialog +import com.margelo.nitro.toadly.github.GitHubService +import kotlin.collections.Map @DoNotStrip class Toadly : HybridToadlySpec() { private var hasSetupBeenCalled = false - private var jsLogs = mutableListOf() - private var githubToken = "" - private var repoOwner = "" - private var repoName = "" + private val jsLogs = mutableListOf() + private var githubToken: String = "" + private var repoOwner: String = "" + private var repoName: String = "" + private lateinit var githubService: GitHubService override fun setup(githubToken: String, repoOwner: String, repoName: String) { if (hasSetupBeenCalled) { @@ -22,6 +26,7 @@ class Toadly : HybridToadlySpec() { this.githubToken = githubToken this.repoOwner = repoOwner this.repoName = repoName + this.githubService = GitHubService(githubToken, repoOwner, repoName) LoggingService.info("Setting up Toadly with GitHub integration") } @@ -33,7 +38,16 @@ class Toadly : HybridToadlySpec() { override fun createIssueWithTitle(title: String, reportType: String?) { LoggingService.info("Creating issue with title: $title, type: ${reportType ?: "bug"}") - // GitHub integration will be implemented later + + val description = jsLogs.joinToString("\n") + val type = reportType ?: "bug" + + Thread { + val success = githubService.createIssue(title, description, type) + if (!success) { + LoggingService.info("Failed to create GitHub issue") + } + }.start() } override fun show() { diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index 85365d7..ae70faf 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -15,7 +15,15 @@ import com.margelo.nitro.toadly.LoggingService import com.margelo.nitro.toadly.R class BugReportDialog(private val context: Context, private val onSubmit: (String, String) -> Unit) { - private val reportTypes = arrayOf("🐞 Bug", "💡 Suggestion", "❓ Question") + + private val reportTypesMap = mapOf( + "🐞 Bug" to "bug", + "💡 Suggestion" to "enhancement", + "❓ Question" to "question" + ) + + + private val reportTypeDisplays = reportTypesMap.keys.toTypedArray() fun show() { Handler(Looper.getMainLooper()).post { @@ -31,7 +39,8 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin val closeButton = layout.findViewById(R.id.closeButton) val sendButton = layout.findViewById(R.id.sendButton) - val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, reportTypes) + // Set up the spinner with report types + val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, reportTypeDisplays) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) reportTypeSpinner.adapter = adapter @@ -42,7 +51,8 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin sendButton.setOnClickListener { val email = emailEditText.text?.toString() ?: "" val description = descriptionEditText.text?.toString() ?: "" - val reportType = reportTypes[reportTypeSpinner.selectedItemPosition] + val selectedTypeDisplay = reportTypeSpinner.selectedItem.toString() + val typeLabel = reportTypesMap[selectedTypeDisplay] ?: "bug" // Get GitHub label without emoji if (email.isEmpty() || description.isEmpty()) { Toast.makeText(context, "Please fill all fields", Toast.LENGTH_SHORT).show() @@ -55,7 +65,7 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin description } - onSubmit(title, reportType.split(" ")[0].lowercase()) + onSubmit(title, typeLabel) // Pass the GitHub label, not the display string Toast.makeText(context, "Bug report submitted", Toast.LENGTH_SHORT).show() dialog.dismiss() } diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt new file mode 100644 index 0000000..73a6823 --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt @@ -0,0 +1,75 @@ +package com.margelo.nitro.toadly.github + +import com.margelo.nitro.toadly.LoggingService +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import org.json.JSONArray +import java.io.IOException +import java.util.Date + +class GitHubService( + private val token: String, + private val repoOwner: String, + private val repoName: String +) { + private val client = OkHttpClient() + private val baseUrl = "https://api.github.com" + private val jsonMediaType = "application/json; charset=utf-8".toMediaType() + + fun createIssue(title: String, description: String, type: String): Boolean { + val url = "$baseUrl/repos/$repoOwner/$repoName/issues" + + val issueBody = buildIssueBody(description, type) + val labels = JSONArray().apply { + put(type.lowercase()) + } + val jsonBody = JSONObject().apply { + put("title", title) + put("body", issueBody) + put("labels", labels) + } + + val request = Request.Builder() + .url(url) + .addHeader("Authorization", "token $token") + .addHeader("Accept", "application/vnd.github.v3+json") + .post(jsonBody.toString().toRequestBody(jsonMediaType)) + .build() + + return try { + val response = client.newCall(request).execute() + val success = response.isSuccessful + if (success) { + LoggingService.info("Successfully created GitHub issue: $title") + } else { + val responseBody = response.body?.string() ?: "" + LoggingService.info("Failed to create GitHub issue. Status: ${response.code}. Body: $responseBody") + response.body?.close() + } + response.body?.close() + success + } catch (e: IOException) { + LoggingService.info("Error creating GitHub issue: ${e.message}") + false + } + } + + private fun buildIssueBody(description: String, type: String): String { + return """ + |## Bug Report + | + |### Type + |$type + | + |### Description + |$description + | + |### System Information + |* Platform: Android + |* Report Time: ${Date()} + """.trimMargin() + } +} From c3623206d426f527fc633f6a14b8090fcbfb8d5a Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 16:11:40 +0300 Subject: [PATCH 09/13] fix input field --- android/src/main/res/layout/dialog_bug_report.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/res/layout/dialog_bug_report.xml b/android/src/main/res/layout/dialog_bug_report.xml index 4b0cba9..16ed60b 100644 --- a/android/src/main/res/layout/dialog_bug_report.xml +++ b/android/src/main/res/layout/dialog_bug_report.xml @@ -71,7 +71,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" - android:padding="6dp" + android:padding="16dp" android:background="@drawable/edit_text_background" /> From 29da2c3482655dbabce0640bbc5a8fef640f2fdd Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 16:28:06 +0300 Subject: [PATCH 10/13] match reporting with ios --- .../margelo/nitro/toadly/LoggingService.kt | 9 ++ .../java/com/margelo/nitro/toadly/Toadly.kt | 30 +++- .../toadly/github/GitHubIssueTemplate.kt | 153 ++++++++++++++++++ .../nitro/toadly/github/GitHubService.kt | 46 +++--- 4 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt diff --git a/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt index b000f42..8429609 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/LoggingService.kt @@ -69,6 +69,15 @@ object LoggingService { } } + /** + * Get all collected logs as a single string + */ + fun getLogs(): String { + synchronized(logs) { + return logs.joinToString("\n") + } + } + /** * Clear all stored logs */ diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index f767750..c4cf99f 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -39,13 +39,35 @@ class Toadly : HybridToadlySpec() { override fun createIssueWithTitle(title: String, reportType: String?) { LoggingService.info("Creating issue with title: $title, type: ${reportType ?: "bug"}") - val description = jsLogs.joinToString("\n") + val description = "User submitted bug report" val type = reportType ?: "bug" + val jsLogsContent = jsLogs.joinToString("\n") + val nativeLogs = LoggingService.getLogs() + + val currentActivity = getCurrentActivity() + if (currentActivity == null) { + LoggingService.error("Cannot create GitHub issue: no activity context found") + return + } Thread { - val success = githubService.createIssue(title, description, type) - if (!success) { - LoggingService.info("Failed to create GitHub issue") + try { + val success = githubService.createIssue( + context = currentActivity, + title = title, + details = description, + jsLogs = jsLogsContent, + nativeLogs = nativeLogs, + reportType = type + ) + + if (success) { + LoggingService.info("Successfully created GitHub issue") + } else { + LoggingService.info("Failed to create GitHub issue") + } + } catch (e: Exception) { + LoggingService.error("Error creating GitHub issue: ${e.message}") } }.start() } diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt new file mode 100644 index 0000000..d1a0831 --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt @@ -0,0 +1,153 @@ +package com.margelo.nitro.toadly.github + +import android.os.Build +import android.content.Context +import android.content.pm.PackageManager +import android.os.Environment +import android.os.StatFs +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class GitHubIssueTemplate { + companion object { + fun generateIssueBody( + context: Context, + email: String, + details: String, + jsLogs: String, + nativeLogs: String, + reportType: String? = null + ): String { + val packageInfo = try { + context.packageManager.getPackageInfo(context.packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + null + } + + val appVersion = packageInfo?.versionName ?: "Unknown" + val buildNumber = packageInfo?.versionCode?.toString() ?: "Unknown" + val deviceModel = Build.MODEL + val systemName = "Android" + val systemVersion = Build.VERSION.RELEASE + val deviceName = Build.DEVICE + val deviceIdentifier = Build.FINGERPRINT + + val timestamp = Date() + val dateFormatter = SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US) + val dateString = dateFormatter.format(timestamp) + + // Get memory information + val memoryInfo = android.app.ActivityManager.MemoryInfo() + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager + activityManager.getMemoryInfo(memoryInfo) + val totalMemory = formatSize(memoryInfo.totalMem) + + // Get screen information + val displayMetrics = context.resources.displayMetrics + val screenWidth = displayMetrics.widthPixels + val screenHeight = displayMetrics.heightPixels + val screenDensity = displayMetrics.density + + // Get locale information + val locale = Locale.getDefault() + val language = locale.language + val region = locale.country + + // Get disk space information + val freeSpace = getFreeSpaceInBytes() + val totalSpace = getTotalSpaceInBytes() + val freeSpaceString = formatSize(freeSpace) + val totalSpaceString = formatSize(totalSpace) + + // Get report type information + val reportTypeText = reportType ?: "Bug" + val reportTypeIcon = getIconForReportType(reportType) + + var issueBody = """ +### Description +$details + +### Report Information +| Property | Value | +| ----- | ----- | +| Report Type | $reportTypeIcon $reportTypeText | +| Email | $email | +| Timestamp | $dateString | + +### Device & App Information +| Property | Value | +| ----- | ----- | +| App Version | $appVersion ($buildNumber) | +| Device Model | $deviceModel | +| Device Name | $deviceName | +| OS | $systemName $systemVersion | +| Device ID | $deviceIdentifier | +| Memory | $totalMemory | +| Free Disk Space | $freeSpaceString / $totalSpaceString | +| Screen | ${screenWidth}x${screenHeight} @${screenDensity}x | +| Language | ${language}_${region} | +""" + + issueBody += """ + +### Logs + +#### JavaScript Logs +``` +$jsLogs +``` + +#### Native Logs +``` +$nativeLogs +``` +""" + + return issueBody + } + + private fun getIconForReportType(reportType: String?): String { + if (reportType == null) return "🐛" + + return when (reportType.lowercase()) { + "bug" -> "🐛" + "suggestion" -> "💡" + "crash" -> "🚨" + "question" -> "❓" + else -> "🐛" + } + } + + private fun formatSize(size: Long): String { + val kb = 1024L + val mb = kb * 1024 + val gb = mb * 1024 + + return when { + size >= gb -> String.format("%.2f GB", size.toFloat() / gb) + size >= mb -> String.format("%.2f MB", size.toFloat() / mb) + size >= kb -> String.format("%.2f KB", size.toFloat() / kb) + else -> "$size bytes" + } + } + + private fun getFreeSpaceInBytes(): Long { + try { + val stat = StatFs(Environment.getExternalStorageDirectory().path) + return stat.availableBlocksLong * stat.blockSizeLong + } catch (e: Exception) { + return 0 + } + } + + private fun getTotalSpaceInBytes(): Long { + try { + val stat = StatFs(Environment.getExternalStorageDirectory().path) + return stat.blockCountLong * stat.blockSizeLong + } catch (e: Exception) { + return 0 + } + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt index 73a6823..f15ac2b 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt @@ -1,5 +1,6 @@ package com.margelo.nitro.toadly.github +import android.content.Context import com.margelo.nitro.toadly.LoggingService import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -18,14 +19,37 @@ class GitHubService( private val client = OkHttpClient() private val baseUrl = "https://api.github.com" private val jsonMediaType = "application/json; charset=utf-8".toMediaType() + + private val labelMap = mapOf( + "🐞 Bug" to "bug", + "💡 Suggestion" to "enhancement", + "❓ Question" to "question" + ) - fun createIssue(title: String, description: String, type: String): Boolean { + fun createIssue( + context: Context, + title: String, + details: String, + jsLogs: String, + nativeLogs: String, + reportType: String + ): Boolean { val url = "$baseUrl/repos/$repoOwner/$repoName/issues" - val issueBody = buildIssueBody(description, type) + val issueBody = GitHubIssueTemplate.generateIssueBody( + context = context, + email = "auto-generated@toadly.app", // TODO: Update with other + details = details, + jsLogs = jsLogs, + nativeLogs = nativeLogs, + reportType = reportType + ) + + val label = labelMap[reportType] ?: "bug" // Default to "bug" if type not found in map val labels = JSONArray().apply { - put(type.lowercase()) + put(label) } + val jsonBody = JSONObject().apply { put("title", title) put("body", issueBody) @@ -56,20 +80,4 @@ class GitHubService( false } } - - private fun buildIssueBody(description: String, type: String): String { - return """ - |## Bug Report - | - |### Type - |$type - | - |### Description - |$description - | - |### System Information - |* Platform: Android - |* Report Time: ${Date()} - """.trimMargin() - } } From e1a0ef165c282bebf7d2b5a9b4c623538cb797e7 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 17:02:24 +0300 Subject: [PATCH 11/13] fix email usage --- .../java/com/margelo/nitro/toadly/Toadly.kt | 45 ++++++++++++++++++- .../nitro/toadly/dialog/BugReportDialog.kt | 6 +-- .../nitro/toadly/github/GitHubService.kt | 8 ++-- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index c4cf99f..aff1264 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -41,10 +41,12 @@ class Toadly : HybridToadlySpec() { val description = "User submitted bug report" val type = reportType ?: "bug" + val email = "auto-generated@toadly.app" val jsLogsContent = jsLogs.joinToString("\n") val nativeLogs = LoggingService.getLogs() val currentActivity = getCurrentActivity() + if (currentActivity == null) { LoggingService.error("Cannot create GitHub issue: no activity context found") return @@ -56,6 +58,7 @@ class Toadly : HybridToadlySpec() { context = currentActivity, title = title, details = description, + email = email, jsLogs = jsLogsContent, nativeLogs = nativeLogs, reportType = type @@ -82,11 +85,49 @@ class Toadly : HybridToadlySpec() { return } - BugReportDialog(currentActivity) { title, reportType -> - createIssueWithTitle(title, reportType) + BugReportDialog(currentActivity) { title, reportType, email -> + createIssueWithEmailAndTitle(title, reportType, email) }.show() } + private fun createIssueWithEmailAndTitle(title: String, reportType: String, email: String) { + LoggingService.info("Creating issue with title: $title, type: $reportType, email: $email") + + val description = "User submitted bug report" + val type = reportType + val jsLogsContent = jsLogs.joinToString("\n") + val nativeLogs = LoggingService.getLogs() + + val currentActivity = getCurrentActivity() + + if (currentActivity == null) { + LoggingService.error("Cannot create GitHub issue: no activity context found") + return + } + + Thread { + try { + val success = githubService.createIssue( + context = currentActivity, + title = title, + details = description, + email = email, + jsLogs = jsLogsContent, + nativeLogs = nativeLogs, + reportType = type + ) + + if (success) { + LoggingService.info("Successfully created GitHub issue") + } else { + LoggingService.info("Failed to create GitHub issue") + } + } catch (e: Exception) { + LoggingService.error("Error creating GitHub issue: ${e.message}") + } + }.start() + } + override fun crashNative() { LoggingService.warn("Crash native requested - this is for testing only") throw RuntimeException("Manually triggered crash from Toadly") diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index ae70faf..0f3820b 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -14,11 +14,11 @@ import androidx.appcompat.widget.AppCompatSpinner import com.margelo.nitro.toadly.LoggingService import com.margelo.nitro.toadly.R -class BugReportDialog(private val context: Context, private val onSubmit: (String, String) -> Unit) { +class BugReportDialog(private val context: Context, private val onSubmit: (String, String, String) -> Unit) { private val reportTypesMap = mapOf( "🐞 Bug" to "bug", - "💡 Suggestion" to "enhancement", + "💡 Suggestion" to "suggestion", "❓ Question" to "question" ) @@ -65,7 +65,7 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin description } - onSubmit(title, typeLabel) // Pass the GitHub label, not the display string + onSubmit(title, typeLabel, email) Toast.makeText(context, "Bug report submitted", Toast.LENGTH_SHORT).show() dialog.dismiss() } diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt index f15ac2b..983b63f 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt @@ -23,7 +23,8 @@ class GitHubService( private val labelMap = mapOf( "🐞 Bug" to "bug", "💡 Suggestion" to "enhancement", - "❓ Question" to "question" + "❓ Question" to "question", + "crash" to "crash" ) fun createIssue( @@ -32,13 +33,14 @@ class GitHubService( details: String, jsLogs: String, nativeLogs: String, - reportType: String + reportType: String, + email: String ): Boolean { val url = "$baseUrl/repos/$repoOwner/$repoName/issues" val issueBody = GitHubIssueTemplate.generateIssueBody( context = context, - email = "auto-generated@toadly.app", // TODO: Update with other + email = email, details = details, jsLogs = jsLogs, nativeLogs = nativeLogs, From 85140440e9d220eaf729449e4ac3d31d075c37f8 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 17:24:54 +0300 Subject: [PATCH 12/13] fix instance dec --- android/src/main/java/com/margelo/nitro/toadly/Toadly.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt index aff1264..5f072df 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/Toadly.kt @@ -9,7 +9,7 @@ import com.margelo.nitro.toadly.github.GitHubService import kotlin.collections.Map @DoNotStrip -class Toadly : HybridToadlySpec() { +object Toadly : HybridToadlySpec() { private var hasSetupBeenCalled = false private val jsLogs = mutableListOf() private var githubToken: String = "" From d03f3cc71c8d13e91711b4e58542ca12804dceb5 Mon Sep 17 00:00:00 2001 From: Mihai Lapuste Date: Sun, 13 Apr 2025 17:32:05 +0300 Subject: [PATCH 13/13] fix labels --- .../java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt | 2 +- .../com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt | 3 +-- .../java/com/margelo/nitro/toadly/github/GitHubService.kt | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt index 0f3820b..4575714 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/dialog/BugReportDialog.kt @@ -18,7 +18,7 @@ class BugReportDialog(private val context: Context, private val onSubmit: (Strin private val reportTypesMap = mapOf( "🐞 Bug" to "bug", - "💡 Suggestion" to "suggestion", + "💡 Suggestion" to "enhancement", "❓ Question" to "question" ) diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt index d1a0831..2c78770 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubIssueTemplate.kt @@ -112,8 +112,7 @@ $nativeLogs return when (reportType.lowercase()) { "bug" -> "🐛" - "suggestion" -> "💡" - "crash" -> "🚨" + "enhancement" -> "💡" "question" -> "❓" else -> "🐛" } diff --git a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt index 983b63f..43665d6 100644 --- a/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt +++ b/android/src/main/java/com/margelo/nitro/toadly/github/GitHubService.kt @@ -21,9 +21,9 @@ class GitHubService( private val jsonMediaType = "application/json; charset=utf-8".toMediaType() private val labelMap = mapOf( - "🐞 Bug" to "bug", - "💡 Suggestion" to "enhancement", - "❓ Question" to "question", + "bug" to "bug", + "enhancement" to "enhancement", + "question" to "question", "crash" to "crash" )