fix crashes

This commit is contained in:
Phillip Thelen 2024-02-26 16:02:40 +01:00
parent 10820923a1
commit 5a61b26ea1
18 changed files with 81 additions and 45 deletions

View file

@ -16,7 +16,7 @@
<application
android:name=".HabiticaApplication"
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"

View file

@ -18,7 +18,7 @@
<application
android:name=".HabiticaApplication"
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

View file

@ -111,7 +111,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "com.google.accompanist:accompanist-themeadapter-material:$accompanist_version"
implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.material3:material3:1.2.0"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'com.google.android.play:core:1.10.3'
@ -127,6 +127,8 @@ dependencies {
implementation project(':shared')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.gu.android:toolargetool:0.3.0'
}
android {

View file

@ -25,6 +25,7 @@ import com.google.android.gms.wearable.Wearable
import com.google.firebase.installations.FirebaseInstallations
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
import com.gu.toolargetool.TooLargeTool
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.extensions.DateUtils
import com.habitrpg.android.habitica.helpers.AdHandler
@ -113,10 +114,12 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
override fun onCreate() {
super.onCreate()
lifecycleTracker = ApplicationLifecycleTracker(sharedPrefs)
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleTracker)
if (!BuildConfig.DEBUG) {
TooLargeTool.startLogging(this)
try {
Analytics.initialize(this)
} catch (ignored: Resources.NotFoundException) {

View file

@ -17,5 +17,9 @@ fun Resources.forceLocale(activity: BaseActivity, locale: Locale) {
}
updateConfiguration(configuration, displayMetrics)
Firebase.crashlytics.setCustomKey("language", locale.toLanguageTag())
try {
Firebase.crashlytics.setCustomKey("language", locale.toLanguageTag())
} catch (_: IllegalStateException) {
// issue with getting firebase
}
}

View file

@ -119,11 +119,13 @@ class AppConfigManager(contentRepository: ContentRepository?) : com.habitrpg.com
fun activePromo(): HabiticaPromotion? {
var promo: HabiticaPromotion? = null
for (event in worldState?.events ?: listOf(worldState?.currentEvent)) {
if (event == null) return null
val thisPromo = getHabiticaPromotionFromKey(event.promo ?: event.eventKey ?: "", event.start, event.end)
if (thisPromo != null) {
promo = thisPromo
if (worldState?.isValid == true) {
for (event in worldState?.events ?: listOf(worldState?.currentEvent)) {
if (event == null) return null
val thisPromo = getHabiticaPromotionFromKey(event.promo ?: event.eventKey ?: "", event.start, event.end)
if (thisPromo != null) {
promo = thisPromo
}
}
}
if (promo == null && remoteConfig.getString("activePromo").isNotBlank()) {

View file

@ -51,6 +51,7 @@ open class Customization : RealmObject(), BaseObject {
}
fun getImageName(userSize: String?, hairColor: String?): String? {
if (!this.isValid()) { return null }
if (identifier?.isNotBlank() != true || identifier == "none" || identifier == "0") return null
return when (type) {
"skin" -> return "skin_$identifier"

View file

@ -9,13 +9,13 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding
import com.habitrpg.android.habitica.databinding.CustomizationSectionFooterBinding
import com.habitrpg.android.habitica.databinding.CustomizationSectionHeaderBinding
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.shared.habitica.models.Avatar
import java.util.Date
@ -198,6 +198,9 @@ class CustomizationRecyclerViewAdapter : androidx.recyclerview.widget.RecyclerVi
}
override fun onClick(v: View) {
if (customization?.isValid != true) {
return
}
if (customization?.isUsable(ownedCustomizations.contains(customization?.id)) == false) {
if (customization?.customizationSet?.contains("timeTravel") == true) {
val dialog = HabiticaAlertDialog(itemView.context)

View file

@ -30,7 +30,6 @@ import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
@ -45,8 +44,10 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.AndroidEntryPoint
import io.realm.kotlin.isValid
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -58,7 +59,6 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.toDuration
@AndroidEntryPoint
@ -238,7 +238,8 @@ class NavigationDrawerFragment : DialogFragment() {
private fun updateSeasonalMenuEntries(gearEvent: WorldStateEvent?, items: List<Item>) {
val market = getItemWithIdentifier(SIDEBAR_SHOPS_MARKET) ?: return
if (items.isNotEmpty() && items.firstOrNull()?.event?.end?.after(Date()) == true) {
val item = items.firstOrNull()
if (item?.isValid() == true && item.event?.end?.after(Date()) == true) {
market.pillText = context?.getString(R.string.something_new)
market.subtitle = context?.getString(R.string.limited_potions_available)
} else {

View file

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
@ -49,7 +50,6 @@ class StableRecyclerFragment :
var adapter: StableRecyclerAdapter? = null
var itemTypeText: String? = null
internal var layoutManager: androidx.recyclerview.widget.GridLayoutManager? = null
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -70,11 +70,11 @@ class StableRecyclerFragment :
)
binding?.refreshLayout?.setOnRefreshListener(this)
layoutManager = androidx.recyclerview.widget.GridLayoutManager(activity, 4)
layoutManager?.spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
val layoutManager = androidx.recyclerview.widget.GridLayoutManager(activity, 4)
layoutManager.spanSizeLookup = object : androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (adapter?.getItemViewType(position) == 0 || adapter?.getItemViewType(position) == 1) {
layoutManager?.spanCount ?: 1
layoutManager.spanCount
} else {
1
}
@ -134,7 +134,7 @@ class StableRecyclerFragment :
if (spanCount == 0) {
spanCount = 1
}
layoutManager?.spanCount = spanCount
(binding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount = spanCount
}
private fun loadItems() {

View file

@ -25,7 +25,6 @@ import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentFaqOverviewBinding
import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@ -34,12 +33,14 @@ import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.helpers.AppTestingLevel
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.models.PlayerTier
import com.jaredrummler.android.device.DeviceName
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.min
@AndroidEntryPoint
class FAQOverviewFragment : BaseMainFragment<FragmentFaqOverviewBinding>() {
@ -143,7 +144,7 @@ class FAQOverviewFragment : BaseMainFragment<FragmentFaqOverviewBinding>() {
ds.isUnderlineText = false
}
}
val startIndex = fullText.indexOf(clickableText)
val startIndex = min(0, fullText.indexOf(clickableText))
val endIndex = startIndex + clickableText.length
spannableString.setSpan(clickableSpan, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

View file

@ -25,23 +25,31 @@ import javax.inject.Inject
class MainUserViewModel @Inject constructor(private val authenticationHandler: AuthenticationHandler, val userRepository: UserRepository, val socialRepository: SocialRepository) {
val formattedUsername: CharSequence?
get() = user.value?.formattedUsername
get() = validatedUser?.formattedUsername
val userID: String
get() = user.value?.id ?: authenticationHandler.currentUserID ?: ""
get() = validatedUser?.id ?: authenticationHandler.currentUserID ?: ""
val username: CharSequence
get() = user.value?.username ?: ""
get() = validatedUser?.username ?: ""
val displayName: CharSequence
get() = user.value?.profile?.name ?: ""
get() = validatedUser?.profile?.name ?: ""
val partyID: String?
get() = user.value?.party?.id
get() = validatedUser?.party?.id
val isUserFainted: Boolean
get() = (user.value?.stats?.hp ?: 1.0) == 0.0
get() = (validatedUser?.stats?.hp ?: 1.0) == 0.0
val isUserInParty: Boolean
get() = user.value?.hasParty == true
get() = validatedUser?.hasParty == true
val mirrorGroupTasks: List<String>
get() = user.value?.preferences?.tasks?.mirrorGroupTasks ?: emptyList()
get() = validatedUser?.preferences?.tasks?.mirrorGroupTasks ?: emptyList()
val user: LiveData<User?> = userRepository.getUser().asLiveData()
private val validatedUser: User?
get() {
val u = this.user.value
if (u?.isValid() == true) {
return u
}
return null
}
var currentTeamPlan = MutableSharedFlow<TeamPlan?>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST

View file

@ -7,6 +7,7 @@ import android.widget.TextView
import androidx.preference.ListPreference
import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.extensions.setScaledPadding
import kotlin.math.min
class HabiticaListPreference : ListPreference {
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
@ -23,8 +24,9 @@ class HabiticaListPreference : ListPreference {
val subtitleText = TextView(context)
subtitleText.setText(R.string.cds_subtitle)
val builder = AlertDialog.Builder(context).setSingleChoiceItems(entries, getValueIndex() + 1) { dialog, index ->
if (callChangeListener(entryValues[index - 1].toString())) {
setValueIndex(index - 1)
val actualIndex = min(0, index - 1)
if (callChangeListener(entryValues[actualIndex].toString())) {
setValueIndex(actualIndex)
}
dialog.dismiss()
}

View file

@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.BadTokenException
import android.view.animation.AccelerateInterpolator
import android.widget.Button
import android.widget.LinearLayout
@ -328,10 +329,14 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
}
private fun addToQueue(dialog: HabiticaAlertDialog) {
if (checkIfQueueAvailable()) {
dialog.show()
try {
if (checkIfQueueAvailable()) {
dialog.show()
}
dialogQueue.add(dialog)
} catch (e: BadTokenException) {
// can't show anything
}
dialogQueue.add(dialog)
}
private fun checkIfQueueAvailable(): Boolean {

View file

@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.NullPointerException
import java.util.regex.Matcher
import java.util.regex.Pattern
@ -93,14 +92,17 @@ object MarkdownParser {
}
val preProcessedInput = processMarkdown(input)
val hashCode = preProcessedInput.hashCode()
try {
if (cache.containsKey(hashCode)) {
return cache[hashCode] ?: SpannableString(preProcessedInput)
var hashCode: Int? = null
if (preProcessedInput.length < 500) {
// caching too large inputs. Since these are unlikely to be in a list, this is fine
hashCode = preProcessedInput.hashCode()
try {
if (cache.containsKey(hashCode)) {
return cache[hashCode] ?: SpannableString(preProcessedInput)
}
} catch (_: NullPointerException) {
// Sometimes happens
}
} catch (_: NullPointerException) {
// Sometimes happens
}
val text = EmojiParser.parseEmojis(preProcessedInput) ?: preProcessedInput
// Adding this space here bc for some reason some markdown is not rendered correctly when the whole string is supposed to be formatted

View file

@ -17,7 +17,9 @@ interface Avatar {
get() = if (username != null) "@$username" else null
val gemCount: Int
get() = (this.balance * 4).toInt()
get() = if (this.isValid()) {
(this.balance * 4).toInt()
} else { 0 }
val costume: AvatarOutfit?
get() = items?.gear?.costume

View file

@ -8,7 +8,7 @@
<application
android:name="com.habitrpg.wearos.habitica.MainApplication"
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"

View file

@ -14,10 +14,10 @@ import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.Wearable
import com.habitrpg.android.habitica.databinding.ActivityWrapperBinding
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.wearos.habitica.managers.AppStateManager
import com.habitrpg.wearos.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.wearos.habitica.ui.views.IndeterminateProgressView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -110,7 +110,7 @@ abstract class BaseActivity<B : ViewBinding, VM : BaseViewModel> : ComponentActi
data: ByteArray?,
function: ((Boolean) -> Unit)? = null
) {
lifecycleScope.launch(Dispatchers.IO) {
lifecycleScope.launchCatching {
val info = Tasks.await(
capabilityClient.getCapability(
permission,