1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 03:29:08 -05:00

Add installation id to crashlytics and bug report emails (#2024)

This commit is contained in:
Mikołaj Pich 2022-10-27 12:41:33 +02:00 committed by GitHub
parent 3925a6261b
commit 22a4f509dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 90 additions and 57 deletions

View File

@ -8,15 +8,7 @@ import javax.inject.Singleton
@Suppress("UNUSED_PARAMETER")
class AnalyticsHelper @Inject constructor() {
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
// do nothing
}
fun setCurrentScreen(activity: Activity, name: String?) {
// do nothing
}
fun popCurrentScreen(name: String?) {
// do nothing
}
fun logEvent(name: String, vararg params: Pair<String, Any?>) = Unit
fun setCurrentScreen(activity: Activity, name: String?) = Unit
fun popCurrentScreen(name: String?) = Unit
}

View File

@ -3,26 +3,38 @@ package io.github.wulkanowy.utils
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.huawei.agconnect.crash.AGConnectCrash
import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) {
private val analytics by lazy { HiAnalytics.getInstance(context) }
private val connectCrash by lazy { AGConnectCrash.getInstance() }
init {
if (!appInfo.isDebug) {
connectCrash.setUserId(preferencesRepository.installationId)
}
}
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply {
params.forEach {
if (it.second == null) return@forEach
when (it.second) {
is String, is String? -> putString(it.first, it.second as String)
is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
params.forEach { (key, value) ->
if (value == null) return@forEach
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
}
}
analytics.onEvent(name, this)

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree
import fr.bipi.tressence.common.StackTraceRecorder
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
@ -22,16 +23,10 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
// Disabled due to a bug in the Huawei library
/*connectCrash.setCustomKey("priority", priority)
connectCrash.setCustomKey("tag", tag.orEmpty())
connectCrash.setCustomKey("message", message)
if (t != null) {
connectCrash.recordException(t)
} else {
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
}*/
}
}
}

View File

@ -10,17 +10,16 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
class PreferencesRepository @Inject constructor(
@ApplicationContext val context: Context,
@ -316,6 +315,16 @@ class PreferencesRepository @Inject constructor(
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
}
var installationId: String
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
init {
if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString()
}
}
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
private fun getLong(id: String, default: Int) =
@ -331,23 +340,14 @@ class PreferencesRepository @Inject constructor(
private fun getBoolean(id: String, default: Int) =
sharedPref.getBoolean(id, context.resources.getBoolean(default))
private fun getBoolean(id: Int, default: Boolean) =
sharedPref.getBoolean(context.getString(id), default)
private companion object {
private const val PREF_KEY_INSTALLATION_ID = "installation_id"
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
}
}

View File

@ -4,7 +4,6 @@ import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
@ -15,6 +14,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.DialogErrorBinding
import io.github.wulkanowy.utils.*
import javax.inject.Inject
@ -25,6 +25,9 @@ class ErrorDialog : DialogFragment() {
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object {
private const val ARGUMENT_KEY = "error"
@ -36,7 +39,7 @@ class ErrorDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
val binding = DialogErrorBinding.inflate(LayoutInflater.from(context))
val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error)
return getAlertDialog(binding, error).apply {
@ -99,7 +102,8 @@ class ErrorDialog : DialogFragment() {
R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}"
"${appInfo.versionName}-${appInfo.buildFlavor}",
preferencesRepository.installationId,
) + "\n" + content,
onActivityNotFound = {
requireContext().openInternetBrowser(

View File

@ -6,6 +6,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentAboutBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
@ -30,6 +31,9 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
override val versionRes: Triple<String, String, Drawable?>?
get() = context?.run {
val buildTimestamp =
@ -185,7 +189,8 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}"
"${appInfo.versionName}-${appInfo.buildFlavor}",
preferencesRepository.installationId,
),
onActivityNotFound = {
requireContext().openInternetBrowser(

View File

@ -10,6 +10,7 @@ import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -32,6 +33,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object {
fun newInstance() = LoginFormFragment()
}
@ -260,8 +264,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
appInfo.versionName,
"${appInfo.versionName}-${appInfo.buildFlavor}",
"$formHostValue/$formHostSymbol",
preferencesRepository.installationId,
lastError
)
)

View File

@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -32,6 +33,9 @@ class LoginStudentSelectFragment :
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object {
const val ARG_STUDENTS = "STUDENTS"
@ -111,10 +115,12 @@ class LoginStudentSelectFragment :
email = "wulkanowyinc@gmail.com",
subject = requireContext().getString(R.string.login_email_subject),
body = requireContext().getString(
R.string.login_email_text, appInfo.systemModel,
R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
appInfo.versionName,
"${appInfo.versionName}-${appInfo.buildFlavor}",
"Select users to log in",
preferencesRepository.installationId,
lastError
)
)

View File

@ -13,6 +13,7 @@ import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -34,6 +35,9 @@ class LoginSymbolFragment :
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object {
private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
@ -159,8 +163,9 @@ class LoginSymbolFragment :
R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(),
appInfo.versionName,
"${appInfo.versionName}-${appInfo.buildFlavor}",
"$host/${binding.loginSymbolName.text}",
preferencesRepository.installationId,
lastError
)
)

View File

@ -71,7 +71,7 @@
<string name="login_contact_discord">Discord</string>
<string name="login_email_intent_title">Send email</string>
<string name="login_email_subject" translatable="false">Zgłoszenie: Problemy z logowaniem</string>
<string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nNazwa szkoły i miejscowość: </string>
<string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nIdentyfikator instalacji: %5$s\nOstatni błąd: %6$s\n\nNazwa szkoły i miejscowość: </string>
<string name="login_recover_warning">Make sure you select the correct UONET+ register variation!</string>
<string name="login_recover_button">I forgot my password</string>
<string name="login_recover_title">Recover your account</string>
@ -512,7 +512,7 @@
<string name="about_homepage_summary">Visit the website and help develop the application</string>
<string name="about_licenses">Licenses</string>
<string name="about_licenses_summary">Licenses of libraries used in the application</string>
<string name="about_feedback_template" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\n\nTreść zgłoszenia:</string>
<string name="about_feedback_template" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nIdentyfikator instalacji: %4$s\nTreść zgłoszenia:</string>
<!--Licenses-->

View File

@ -4,25 +4,37 @@ import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) {
private val analytics by lazy { FirebaseAnalytics.getInstance(context) }
private val crashlytics by lazy { FirebaseCrashlytics.getInstance() }
init {
if (!appInfo.isDebug) {
crashlytics.setUserId(preferencesRepository.installationId)
}
}
fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply {
params.forEach {
if (it.second == null) return@forEach
when (it.second) {
is String, is String? -> putString(it.first, it.second.toString())
is Int, is Int? -> putInt(it.first, it.second as Int)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean)
params.forEach { (key, value) ->
if (value == null) return@forEach
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
}
}
analytics.logEvent(name, this)

View File

@ -23,9 +23,6 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return
crashlytics.setCustomKey("priority", priority)
crashlytics.setCustomKey("tag", tag.orEmpty())
crashlytics.setCustomKey("message", message)
if (t != null) {
crashlytics.recordException(t)
} else {