feat: crash report | various small fixes

This commit is contained in:
rebelonion 2024-05-01 14:45:08 -05:00
parent 31c509f88c
commit 8a0224e6b0
12 changed files with 250 additions and 88 deletions

View file

@ -227,6 +227,11 @@
android:windowSoftInputMode="adjustResize|stateHidden" /> android:windowSoftInputMode="adjustResize|stateHidden" />
<activity android:name=".media.CharacterDetailsActivity" /> <activity android:name=".media.CharacterDetailsActivity" />
<activity android:name=".home.NoInternet" /> <activity android:name=".home.NoInternet" />
<activity android:name=".others.CrashActivity"
android:excludeFromRecents="true"
android:exported="true"
android:process=":error_process"
android:launchMode="singleTask" />
<activity <activity
android:name=".media.anime.ExoplayerView" android:name=".media.anime.ExoplayerView"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"

View file

@ -38,6 +38,7 @@ import logcat.LogPriority
import logcat.LogcatLogger import logcat.LogcatLogger
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.lang.IllegalStateException
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -124,11 +125,16 @@ class App : MultiDexApplication() {
downloadAddonManager = Injekt.get() downloadAddonManager = Injekt.get()
torrentAddonManager.init() torrentAddonManager.init()
downloadAddonManager.init() downloadAddonManager.init()
CommentsAPI.fetchAuthToken() CommentsAPI.fetchAuthToken(this@App)
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager) val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
val scheduler = TaskScheduler.create(this@App, useAlarmManager) val scheduler = TaskScheduler.create(this@App, useAlarmManager)
scheduler.scheduleAllTasks(this@App) try {
scheduler.scheduleAllTasks(this@App)
} catch (e: IllegalStateException) {
Logger.log("Failed to schedule tasks")
Logger.log(e)
}
} }
} }
@ -159,7 +165,7 @@ class App : MultiDexApplication() {
} }
companion object { companion object {
private var instance: App? = null var instance: App? = null
/** Reference to the application context. /** Reference to the application context.
* *

View file

@ -77,7 +77,7 @@ class AnilistQueries {
"""{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}""" """{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
runBlocking { runBlocking {
val anilist = async { val anilist = async {
var response = executeQuery<Query.Media>(query, force = true, show = true) var response = executeQuery<Query.Media>(query, force = true)
if (response != null) { if (response != null) {
fun parse() { fun parse() {
val fetchedMedia = response?.data?.media ?: return val fetchedMedia = response?.data?.media ?: return

View file

@ -1,10 +1,13 @@
package ani.dantotsu.connections.comments package ani.dantotsu.connections.comments
import android.content.Context
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.isOnline
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.toast import ani.dantotsu.toast
import ani.dantotsu.util.Logger
import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.NiceResponse
import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Requests
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -25,6 +28,7 @@ import uy.kohesive.injekt.api.get
object CommentsAPI { object CommentsAPI {
private const val ADDRESS: String = "https://1224665.xyz:443" private const val ADDRESS: String = "https://1224665.xyz:443"
private var isOnline: Boolean = true
var authToken: String? = null var authToken: String? = null
var userId: String? = null var userId: String? = null
var isBanned: Boolean = false var isBanned: Boolean = false
@ -49,7 +53,8 @@ object CommentsAPI {
val json = try { val json = try {
request.get(url) request.get(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to fetch comments") Logger.log(e)
errorMessage("Failed to fetch comments")
return null return null
} }
if (!json.text.startsWith("{")) return null if (!json.text.startsWith("{")) return null
@ -71,7 +76,8 @@ object CommentsAPI {
val json = try { val json = try {
request.get(url) request.get(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to fetch comments") Logger.log(e)
errorMessage("Failed to fetch comments")
return null return null
} }
if (!json.text.startsWith("{")) return null if (!json.text.startsWith("{")) return null
@ -93,7 +99,8 @@ object CommentsAPI {
val json = try { val json = try {
request.get(url) request.get(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to fetch comment") Logger.log(e)
errorMessage("Failed to fetch comment")
return null return null
} }
if (!json.text.startsWith("{")) return null if (!json.text.startsWith("{")) return null
@ -115,7 +122,8 @@ object CommentsAPI {
val json = try { val json = try {
request.post(url) request.post(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to vote") Logger.log(e)
errorMessage("Failed to vote")
return false return false
} }
val res = json.code == 200 val res = json.code == 200
@ -141,7 +149,8 @@ object CommentsAPI {
val json = try { val json = try {
request.post(url, requestBody = body.build()) request.post(url, requestBody = body.build())
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to comment") Logger.log(e)
errorMessage("Failed to comment")
return null return null
} }
val res = json.code == 200 val res = json.code == 200
@ -152,7 +161,8 @@ object CommentsAPI {
val parsed = try { val parsed = try {
Json.decodeFromString<ReturnedComment>(json.text) Json.decodeFromString<ReturnedComment>(json.text)
} catch (e: Exception) { } catch (e: Exception) {
snackString("Failed to parse comment") Logger.log(e)
errorMessage("Failed to parse comment")
return null return null
} }
return Comment( return Comment(
@ -179,7 +189,8 @@ object CommentsAPI {
val json = try { val json = try {
request.delete(url) request.delete(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to delete comment") Logger.log(e)
errorMessage("Failed to delete comment")
return false return false
} }
val res = json.code == 200 val res = json.code == 200
@ -198,7 +209,8 @@ object CommentsAPI {
val json = try { val json = try {
request.put(url, requestBody = body) request.put(url, requestBody = body)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to edit comment") Logger.log(e)
errorMessage("Failed to edit comment")
return false return false
} }
val res = json.code == 200 val res = json.code == 200
@ -214,7 +226,8 @@ object CommentsAPI {
val json = try { val json = try {
request.post(url) request.post(url)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to ban user") Logger.log(e)
errorMessage("Failed to ban user")
return false return false
} }
val res = json.code == 200 val res = json.code == 200
@ -241,7 +254,8 @@ object CommentsAPI {
val json = try { val json = try {
request.post(url, requestBody = body) request.post(url, requestBody = body)
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to report comment") Logger.log(e)
errorMessage("Failed to report comment")
return false return false
} }
val res = json.code == 200 val res = json.code == 200
@ -296,7 +310,8 @@ object CommentsAPI {
return null return null
} }
suspend fun fetchAuthToken(client: OkHttpClient? = null) { suspend fun fetchAuthToken(context: Context, client: OkHttpClient? = null) {
isOnline = isOnline(context)
if (authToken != null) return if (authToken != null) return
val MAX_RETRIES = 5 val MAX_RETRIES = 5
val tokenLifetime: Long = 1000 * 60 * 60 * 24 * 6 // 6 days val tokenLifetime: Long = 1000 * 60 * 60 * 24 * 6 // 6 days
@ -325,7 +340,8 @@ object CommentsAPI {
val parsed = try { val parsed = try {
Json.decodeFromString<AuthResponse>(json.text) Json.decodeFromString<AuthResponse>(json.text)
} catch (e: Exception) { } catch (e: Exception) {
snackString("Failed to login to comments API: ${e.printStackTrace()}") Logger.log(e)
errorMessage("Failed to login to comments API: ${e.printStackTrace()}")
return return
} }
PrefManager.setVal(PrefName.CommentAuthResponse, parsed) PrefManager.setVal(PrefName.CommentAuthResponse, parsed)
@ -345,12 +361,18 @@ object CommentsAPI {
return return
} }
} catch (e: IOException) { } catch (e: IOException) {
snackString("Failed to login to comments API") Logger.log(e)
errorMessage("Failed to login to comments API")
return return
} }
kotlinx.coroutines.delay(60000) kotlinx.coroutines.delay(60000)
} }
snackString("Failed to login after multiple attempts") errorMessage("Failed to login after multiple attempts")
}
private fun errorMessage(reason: String) {
Logger.log(reason)
if (isOnline) snackString(reason)
} }
fun logout() { fun logout() {

View file

@ -38,6 +38,7 @@ import ani.dantotsu.databinding.ItemTitleSearchBinding
import ani.dantotsu.databinding.ItemTitleTextBinding import ani.dantotsu.databinding.ItemTitleTextBinding
import ani.dantotsu.databinding.ItemTitleTrailerBinding import ani.dantotsu.databinding.ItemTitleTrailerBinding
import ani.dantotsu.displayTimer import ani.dantotsu.displayTimer
import ani.dantotsu.isOnline
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.profile.User import ani.dantotsu.profile.User
@ -80,7 +81,7 @@ class MediaInfoFragment : Fragment() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val model: MediaDetailsViewModel by activityViewModels() val model: MediaDetailsViewModel by activityViewModels()
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) || !isOnline(requireContext())
binding.mediaInfoProgressBar.isGone = loaded binding.mediaInfoProgressBar.isGone = loaded
binding.mediaInfoContainer.isVisible = loaded binding.mediaInfoContainer.isVisible = loaded
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight } binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }

View file

@ -27,16 +27,6 @@ class MediaListViewActivity: AppCompatActivity() {
binding = ActivityMediaListViewBinding.inflate(layoutInflater) binding = ActivityMediaListViewBinding.inflate(layoutInflater)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
initActivity(this) initActivity(this)
setContentView(binding.root)
val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface)
val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary)
val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline)
window.statusBarColor = primaryColor
window.navigationBarColor = primaryColor
binding.listAppBar.setBackgroundColor(primaryColor)
binding.listTitle.setTextColor(primaryTextColor)
if (!PrefManager.getVal<Boolean>(PrefName.ImmersiveMode)) { if (!PrefManager.getVal<Boolean>(PrefName.ImmersiveMode)) {
this.window.statusBarColor = this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv) ContextCompat.getColor(this, R.color.nav_bg_inv)
@ -50,6 +40,16 @@ class MediaListViewActivity: AppCompatActivity() {
topMargin = statusBarHeight topMargin = statusBarHeight
} }
} }
setContentView(binding.root)
val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface)
val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary)
val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline)
window.statusBarColor = primaryColor
window.navigationBarColor = primaryColor
binding.listAppBar.setBackgroundColor(primaryColor)
binding.listTitle.setTextColor(primaryTextColor)
val screenWidth = resources.displayMetrics.run { widthPixels / density } val screenWidth = resources.displayMetrics.run { widthPixels / density }
binding.listTitle.text = intent.getStringExtra("title") binding.listTitle.text = intent.getStringExtra("title")
binding.mediaRecyclerView.adapter = MediaAdaptor(0, mediaList, this) binding.mediaRecyclerView.adapter = MediaAdaptor(0, mediaList, this)

View file

@ -30,7 +30,7 @@ class CommentNotificationTask : Task {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
PrefManager.init(context) //make sure prefs are initialized PrefManager.init(context) //make sure prefs are initialized
val client = OkHttpClient() val client = OkHttpClient()
CommentsAPI.fetchAuthToken(client) CommentsAPI.fetchAuthToken(context, client)
val notificationResponse = CommentsAPI.getNotifications(client) val notificationResponse = CommentsAPI.getNotifications(client)
var notifications = notificationResponse?.notifications?.toMutableList() var notifications = notificationResponse?.notifications?.toMutableList()
//if we have at least one reply notification, we need to fetch the media titles //if we have at least one reply notification, we need to fetch the media titles

View file

@ -0,0 +1,30 @@
package ani.dantotsu.others
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.R
import eu.kanade.tachiyomi.util.system.copyToClipboard
class CrashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crash)
val stackTrace = intent.getStringExtra("stackTrace") ?: "No stack trace available"
val reportView = findViewById<EditText>(R.id.crashReportView)
reportView.setText(stackTrace)
reportView.setOnKeyListener(View.OnKeyListener { _, _, _ ->
true // Blocks input from hardware keyboards.
})
val copyButton = findViewById<Button>(R.id.copyButton)
copyButton.setOnClickListener {
copyToClipboard("Crash log", stackTrace)
}
}
}

View file

@ -5,16 +5,20 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import ani.dantotsu.App
import ani.dantotsu.BuildConfig import ani.dantotsu.BuildConfig
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.others.CrashActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger.getDeviceAndAppInfo
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.util.Date import java.util.Date
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.system.exitProcess
object Logger { object Logger {
var file: File? = null var file: File? = null
@ -25,7 +29,7 @@ object Logger {
if (!PrefManager.getVal<Boolean>(PrefName.LogToFile) || file != null) return if (!PrefManager.getVal<Boolean>(PrefName.LogToFile) || file != null) return
file = File(context.getExternalFilesDir(null), "log.txt") file = File(context.getExternalFilesDir(null), "log.txt")
if (file?.exists() == true) { if (file?.exists() == true) {
if (file!!.length() > 1024 * 1024 * 10) { // 10MB if (file!!.length() > 1024 * 1024 * 5) { // 5 MB
file?.delete() file?.delete()
file?.createNewFile() file?.createNewFile()
} }
@ -33,52 +37,8 @@ object Logger {
file?.createNewFile() file?.createNewFile()
} }
file?.appendText("log started\n") file?.appendText("log started\n")
file?.appendText("date/time: ${Date()}\n") file?.appendText(getDeviceAndAppInfo(context))
file?.appendText("device: ${Build.MODEL}\n")
file?.appendText("os version: ${Build.VERSION.RELEASE}\n")
file?.appendText(
"app version: ${
context.packageManager.getPackageInfo(
context.packageName,
0
).versionName
}\n"
)
file?.appendText(
"app version code: ${
context.packageManager.getPackageInfo(
context.packageName,
0
).run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
longVersionCode
else
@Suppress("DEPRECATION") versionCode
}
}\n"
)
file?.appendText("sdk version: ${Build.VERSION.SDK_INT}\n")
file?.appendText("manufacturer: ${Build.MANUFACTURER}\n")
file?.appendText("brand: ${Build.BRAND}\n")
file?.appendText("product: ${Build.PRODUCT}\n")
file?.appendText("device: ${Build.DEVICE}\n")
file?.appendText("hardware: ${Build.HARDWARE}\n")
file?.appendText("host: ${Build.HOST}\n")
file?.appendText("id: ${Build.ID}\n")
file?.appendText("type: ${Build.TYPE}\n")
file?.appendText("user: ${Build.USER}\n")
file?.appendText("tags: ${Build.TAGS}\n")
file?.appendText("time: ${Build.TIME}\n")
file?.appendText("radio: ${Build.getRadioVersion()}\n")
file?.appendText("bootloader: ${Build.BOOTLOADER}\n")
file?.appendText("board: ${Build.BOARD}\n")
file?.appendText("fingerprint: ${Build.FINGERPRINT}\n")
file?.appendText("supported_abis: ${Build.SUPPORTED_ABIS.joinToString()}\n")
file?.appendText("supported_32_bit_abis: ${Build.SUPPORTED_32_BIT_ABIS.joinToString()}\n")
file?.appendText("supported_64_bit_abis: ${Build.SUPPORTED_64_BIT_ABIS.joinToString()}\n")
file?.appendText("is emulator: ${Build.FINGERPRINT.contains("generic")}\n")
file?.appendText("--------------------------------\n")
} catch (e: Exception) { } catch (e: Exception) {
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
file = null file = null
@ -155,15 +115,81 @@ object Logger {
file?.delete() file?.delete()
file = null file = null
} }
fun getDeviceAndAppInfo(context: Context): String {
val pm = context.packageManager
val pkgInfo = pm.getPackageInfo(context.packageName, 0)
val versionName = pkgInfo.versionName
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
pkgInfo.longVersionCode
} else {
@Suppress("DEPRECATION")
pkgInfo.versionCode
}
return buildString {
append("Date/time: ${Date()}\n")
append("Device: ${Build.MODEL}\n")
append("OS version: ${Build.VERSION.RELEASE}\n")
append("App version: $versionName\n")
append("App version code: $versionCode\n")
append("SDK version: ${Build.VERSION.SDK_INT}\n")
append("Manufacturer: ${Build.MANUFACTURER}\n")
append("Brand: ${Build.BRAND}\n")
append("Product: ${Build.PRODUCT}\n")
append("Device: ${Build.DEVICE}\n")
append("Hardware: ${Build.HARDWARE}\n")
append("Host: ${Build.HOST}\n")
append("ID: ${Build.ID}\n")
append("Type: ${Build.TYPE}\n")
append("User: ${Build.USER}\n")
append("Tags: ${Build.TAGS}\n")
append("Time: ${Build.TIME}\n")
append("Radio: ${Build.getRadioVersion()}\n")
append("Bootloader: ${Build.BOOTLOADER}\n")
append("Board: ${Build.BOARD}\n")
append("Fingerprint: ${Build.FINGERPRINT}\n")
append("Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}\n")
append("Supported 32 bit ABIs: ${Build.SUPPORTED_32_BIT_ABIS.joinToString()}\n")
append("Supported 64 bit ABIs: ${Build.SUPPORTED_64_BIT_ABIS.joinToString()}\n")
append("Is emulator: ${Build.FINGERPRINT.contains("generic")}\n")
append("--------------------------------\n")
}
}
} }
class FinalExceptionHandler : Thread.UncaughtExceptionHandler { class FinalExceptionHandler : Thread.UncaughtExceptionHandler {
private val defaultUEH: Thread.UncaughtExceptionHandler? = private val defaultUEH = Thread.getDefaultUncaughtExceptionHandler()
Thread.getDefaultUncaughtExceptionHandler() private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
override fun uncaughtException(t: Thread, e: Throwable) { override fun uncaughtException(t: Thread, e: Throwable) {
Logger.uncaughtException(t, e) val stackTraceString = Log.getStackTraceString(e)
Injekt.get<CrashlyticsInterface>().logException(e) Injekt.get<CrashlyticsInterface>().logException(e)
if (App.instance?.applicationContext != null) {
App.instance?.applicationContext?.let {
val report = StringBuilder()
report.append(getDeviceAndAppInfo(it))
report.append("Thread: ${t.name}\n")
report.append("Exception: ${e.message}\n")
report.append("Stack trace:\n")
report.append(stackTraceString)
val reportString = report.toString()
Logger.uncaughtException(t, Error(reportString))
val intent = Intent(it, CrashActivity::class.java)
if (reportString.length > MAX_STACK_TRACE_SIZE) {
val subStr = reportString.substring(0, MAX_STACK_TRACE_SIZE)
intent.putExtra("stackTrace", subStr)
} else intent.putExtra("stackTrace", reportString)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
it.startActivity(intent)
}
} else {
Logger.log("App context is null")
Logger.uncaughtException(t, e)
}
defaultUEH?.uncaughtException(t, e) defaultUEH?.uncaughtException(t, e)
android.os.Process.killProcess(android.os.Process.myPid())
exitProcess(10)
} }
} }

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="top|center_horizontal"
android:orientation="vertical"
android:paddingTop="48dp"
app:layout_constraintBottom_toTopOf="@+id/copyButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_thin"
android:text="@string/sad"
android:textColor="?attr/colorPrimary"
android:textSize="64sp" />
<TextView
android:layout_width="200dp"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold"
android:text="@string/app_died"
android:textAlignment="center"
android:textColor="?attr/colorPrimary"
android:textSize="32sp" />
<TextView
android:layout_width="200dp"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins"
android:text="@string/an_unexpected_error_occurred"
android:textAlignment="center"
android:textColor="?attr/colorOnBackground" />
<EditText
android:id="@+id/crashReportView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:fontFamily="@font/poppins"
android:inputType="textMultiLine|none"
android:textIsSelectable="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="16dp"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
<Button
android:id="@+id/copyButton"
android:layout_width="wrap_content"
android:layout_height="64dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="64dp"
android:fontFamily="@font/poppins_bold"
android:maxLines="1"
android:text="@string/copy_report"
app:cornerRadius="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -44,11 +44,7 @@
android:fontFamily="@font/poppins_thin" android:fontFamily="@font/poppins_thin"
android:text="@string/sad" android:text="@string/sad"
android:textColor="?attr/colorPrimary" android:textColor="?attr/colorPrimary"
android:textSize="64sp" android:textSize="64sp"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/no_internet" android:id="@+id/no_internet"
@ -56,10 +52,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/no_internet" android:text="@string/no_internet"
android:textAlignment="center" android:textAlignment="center"
android:textColor="?attr/colorOnBackground" android:textColor="?attr/colorOnBackground"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/noInternetSad" />
</LinearLayout> </LinearLayout>
<Button <Button
@ -69,6 +62,7 @@
android:layout_gravity="bottom|center_horizontal" android:layout_gravity="bottom|center_horizontal"
android:layout_margin="128dp" android:layout_margin="128dp"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:maxLines="1"
android:text="@string/refresh" android:text="@string/refresh"
app:cornerRadius="16dp" app:cornerRadius="16dp"
app:icon="@drawable/ic_round_refresh_24" /> app:icon="@drawable/ic_round_refresh_24" />

View file

@ -967,4 +967,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="your_story">Your Story</string> <string name="your_story">Your Story</string>
<string name="your_progress">Your Progress</string> <string name="your_progress">Your Progress</string>
<string name="logout_confirm">Are you sure you want to logout?</string> <string name="logout_confirm">Are you sure you want to logout?</string>
<string name="app_died">APP DIED</string>
<string name="an_unexpected_error_occurred">An unexpected error occurred.\nPlease send a crash report to the developer :)</string>
<string name="copy_report">Copy Report</string>
</resources> </resources>