From 5a61b26ea11868b431a47aad23269922fb5ed8db Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 26 Feb 2024 16:02:40 +0100 Subject: [PATCH] fix crashes --- Habitica/AndroidManifest.xml | 2 +- Habitica/AndroidManifestTesting.xml | 2 +- Habitica/build.gradle | 4 +++- .../habitica/HabiticaBaseApplication.kt | 3 +++ .../extensions/ResourcesExtensions.kt | 6 ++++- .../habitica/helpers/AppConfigManager.kt | 12 ++++++---- .../models/inventory/Customization.kt | 1 + .../CustomizationRecyclerViewAdapter.kt | 5 +++- .../ui/fragments/NavigationDrawerFragment.kt | 7 +++--- .../stable/StableRecyclerFragment.kt | 10 ++++---- .../fragments/support/FAQOverviewFragment.kt | 5 ++-- .../ui/viewmodels/MainUserViewModel.kt | 24 ++++++++++++------- .../ui/views/HabiticaListPreference.kt | 6 +++-- .../ui/views/dialogs/HabiticaAlertDialog.kt | 11 ++++++--- .../common/habitica/helpers/MarkdownParser.kt | 18 +++++++------- .../habitrpg/shared/habitica/models/Avatar.kt | 4 +++- wearos/src/main/AndroidManifest.xml | 2 +- .../habitica/ui/activities/BaseActivity.kt | 4 ++-- 18 files changed, 81 insertions(+), 45 deletions(-) diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index 992e4d321..27d232215 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -16,7 +16,7 @@ diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 60ce4bc24..1c10ed982 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -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 { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt index dcdad5285..014eebd73 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt index 54e9b3081..9af04da29 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt @@ -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 + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt index d17af4f02..332c73eb6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt @@ -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()) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt index dac278cb1..95a174152 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt @@ -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" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt index d07db2a4b..0e356d5a7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt @@ -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) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index 69852cb18..bbfcb49e1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -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) { 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 { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt index 671f3c186..1617bfc79 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragment.kt @@ -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() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt index eb1b4557f..cfb0f2114 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt @@ -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() { @@ -143,7 +144,7 @@ class FAQOverviewFragment : BaseMainFragment() { 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) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index e06f83282..c52d11159 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -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 - get() = user.value?.preferences?.tasks?.mirrorGroupTasks ?: emptyList() + get() = validatedUser?.preferences?.tasks?.mirrorGroupTasks ?: emptyList() val user: LiveData = userRepository.getUser().asLiveData() + private val validatedUser: User? + get() { + val u = this.user.value + if (u?.isValid() == true) { + return u + } + return null + } var currentTeamPlan = MutableSharedFlow( replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt index 46c677d2d..1b03e2203 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt @@ -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() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt index f38ecc85a..25362c678 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt @@ -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 { diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt index 727bbfdb5..eacc6b5e4 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt @@ -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 diff --git a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt index deb23816c..aceac0278 100644 --- a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt +++ b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt @@ -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 diff --git a/wearos/src/main/AndroidManifest.xml b/wearos/src/main/AndroidManifest.xml index 3601f87e1..6e01a300b 100644 --- a/wearos/src/main/AndroidManifest.xml +++ b/wearos/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ : ComponentActi data: ByteArray?, function: ((Boolean) -> Unit)? = null ) { - lifecycleScope.launch(Dispatchers.IO) { + lifecycleScope.launchCatching { val info = Tasks.await( capabilityClient.getCapability( permission,