Fix various crashes

This commit is contained in:
Phillip Thelen 2021-10-18 09:24:23 +02:00
parent e1406f3a43
commit bb01dc9b7d
18 changed files with 90 additions and 62 deletions

View file

@ -155,7 +155,7 @@ android {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
resConfigs "en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "zh", "zh-rTW"
versionCode 3070
versionCode 3077
versionName "3.4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -900,7 +900,7 @@
<string name="renew_subscription">有料プランを再開する</string>
<string name="thanks_for_subscribing">有料プランにご加入いただきありがとうございます</string>
<string name="subscribe_options_title">有料プランの期間をあなたのご都合に合わせて選びましょう</string>
<string name="ending_on">%@に止める</string>
<string name="ending_on">%sに止める</string>
<string name="not_recurring">くり返さない</string>
<string name="cancelled">キャンセルしました</string>
<string name="send_invites">招待状を送る</string>

View file

@ -1096,6 +1096,6 @@
<string name="sent_card">%s gönderdin</string>
<string name="take_me_back">Beni geri al</string>
<string name="not_now">Şimdi olmaz</string>
<string name="need_help_header_description">Sorularınızın bir başka oyuncu tarafından cevaplanması için% s içinde bir mesaj gönderin.</string>
<string name="need_help_header_description">Sorularınızın bir başka oyuncu tarafından cevaplanması için%s içinde bir mesaj gönderin.</string>
<string name="setup_task_habit_1_notes">Bir Alışkanlık, Günlük İş veya Yapılacak İş</string>
</resources>

View file

@ -103,6 +103,7 @@ abstract class HabiticaBaseApplication : Application() {
}
var builder = ImageLoader.Builder(this)
.transition(CrossfadeTransition())
.allowHardware(false)
.componentRegistry {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder(this@HabiticaBaseApplication))

View file

@ -20,6 +20,7 @@ import org.solovyev.android.checkout.RequestListener
import org.solovyev.android.checkout.ResponseCodes
import retrofit2.HttpException
import java.util.*
import kotlin.math.abs
class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePurchaseVerifier() {
private val apiClient: ApiClient
@ -40,8 +41,8 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
purchasedOrderList.add(purchase.orderId)
verifiedPurchases.add(purchase)
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
val giftedID = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
val gift = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, gift?.second))
}) { throwable: Throwable ->
handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
}
@ -52,8 +53,8 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
purchasedOrderList.add(purchase.orderId)
verifiedPurchases.add(purchase)
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
val giftedID = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
val gift = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, gift?.second))
}) { throwable: Throwable ->
handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
}
@ -104,9 +105,14 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
validationRequest.transaction = Transaction()
validationRequest.transaction?.receipt = purchase.data
validationRequest.transaction?.signature = purchase.signature
if (pendingGifts.containsKey(purchase.sku)) {
validationRequest.gift = IAPGift()
validationRequest.gift?.uuid = pendingGifts[purchase.sku]
pendingGifts[purchase.sku]?.let { gift ->
// If the gift and the purchase happened within 5 minutes, we consider them to match.
// Otherwise the gift is probably an old one that wasn't cleared out correctly
if (abs(gift.first.time - purchase.time) < 300000) {
validationRequest.gift = IAPGift(gift.second)
} else {
removeGift(purchase.sku)
}
}
return validationRequest
}
@ -132,16 +138,16 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
}
private fun loadPendingGifts(): MutableMap<String?, String?> {
val outputMap: MutableMap<String?, String?> = HashMap()
private fun loadPendingGifts(): MutableMap<String, Pair<Date, String>> {
val outputMap: MutableMap<String, Pair<Date, String>> = HashMap()
try {
val jsonString = preferences?.getString(PENDING_GIFTS_KEY, JSONObject().toString()) ?: ""
val jsonObject = JSONObject(jsonString)
val keysItr = jsonObject.keys()
while (keysItr.hasNext()) {
val key = keysItr.next()
val value = jsonObject[key] as String
outputMap[key] = value
val value = jsonObject.getJSONArray(key)
outputMap[key] = Pair(value[0] as Date, value[1] as String)
}
} catch (e: Exception) {
RxErrorHandler.reportError(e)
@ -151,19 +157,19 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
companion object {
private const val PURCHASED_PRODUCTS_KEY = "PURCHASED_PRODUCTS"
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS"
private var pendingGifts: MutableMap<String?, String?> = HashMap()
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS_DATED"
private var pendingGifts: MutableMap<String, Pair<Date, String>> = HashMap()
private var preferences: SharedPreferences? = null
fun addGift(sku: String?, userID: String?) {
pendingGifts[sku] = userID
fun addGift(sku: String, userID: String) {
pendingGifts[sku] = Pair(Date(), userID)
savePendingGifts()
}
private fun removeGift(sku: String): String? {
val giftedID = pendingGifts.remove(sku)
private fun removeGift(sku: String): Pair<Date, String>? {
val gift = pendingGifts.remove(sku)
savePendingGifts()
return giftedID
return gift
}
private fun savePendingGifts() {

View file

@ -25,12 +25,12 @@ import javax.security.cert.CertificateException
// https://stackoverflow.com/a/42716982
class KeyHelper @Throws(NoSuchPaddingException::class, NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class)
constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore: KeyStore) {
constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore: KeyStore?) {
private val aesKeyFromKS: Key?
@Throws(NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class, UnrecoverableKeyException::class)
get() {
return keyStore.getKey(KEY_ALIAS, null) as? SecretKey
return keyStore?.getKey(KEY_ALIAS, null) as? SecretKey
}
init {
@ -48,10 +48,10 @@ constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore
@Throws(NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class)
private fun generateEncryptKey(ctx: Context) {
keyStore.load(null)
keyStore?.load(null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keyStore.containsAlias(KEY_ALIAS)) {
if (keyStore?.containsAlias(KEY_ALIAS) == false) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore)
keyGenerator.init(
KeyGenParameterSpec.Builder(
@ -66,7 +66,7 @@ constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore
keyGenerator.generateKey()
}
} else {
if (!keyStore.containsAlias(KEY_ALIAS)) {
if (keyStore?.containsAlias(KEY_ALIAS) == false) {
// Generate a key pair for encryption
val start = Calendar.getInstance()
val end = Calendar.getInstance()
@ -87,7 +87,7 @@ constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore
@Throws(Exception::class)
private fun rsaEncrypt(secret: ByteArray): ByteArray {
val privateKeyEntry = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
val privateKeyEntry = keyStore?.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
// Encrypt the text
val inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL")
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry?.certificate?.publicKey)
@ -102,7 +102,7 @@ constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore
@Throws(Exception::class)
private fun rsaDecrypt(encrypted: ByteArray): ByteArray {
val privateKeyEntry = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
val privateKeyEntry = keyStore?.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
val output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL")
output.init(Cipher.DECRYPT_MODE, privateKeyEntry?.privateKey)
val cipherInputStream = CipherInputStream(

View file

@ -84,7 +84,7 @@ open class PurchaseHandler(activity: Activity, val analyticsManager: AnalyticsMa
cont.resume(it)
}
} catch (e: NullPointerException) {
cont.resumeWithException(e)
cont.resume(null)
}
if (inventory == null) cont.resume(null)
}

View file

@ -5,10 +5,10 @@ import android.media.AudioManager
import android.media.MediaPlayer
import java.io.File
class SoundFile(val theme: String, private val fileName: String) : MediaPlayer.OnCompletionListener {
class SoundFile(val theme: String, private val fileName: String) {
private var player: MediaPlayer? = null
var file: File? = null
private var playerPrepared: Boolean = false
private var isPlaying: Boolean = false
val webUrl: String
get() = "https://s3.amazonaws.com/habitica-assets/mobileApp/sounds/$theme/$fileName.mp3"
@ -17,37 +17,37 @@ class SoundFile(val theme: String, private val fileName: String) : MediaPlayer.O
get() = theme + "_" + fileName + ".mp3"
fun play() {
if (isPlaying || file?.path == null) {
if (player?.isPlaying == true || file?.path == null) {
return
}
val m = MediaPlayer()
if (player?.isPlaying == false) {
player?.release()
player = null
}
m.setOnCompletionListener { mp ->
isPlaying = false
player = MediaPlayer()
player?.setOnCompletionListener { mp ->
mp.release()
player = null
}
try {
m.setDataSource(file?.path)
player?.setDataSource(file?.path)
val attributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build()
m.setAudioAttributes(attributes)
m.prepare()
player?.setAudioAttributes(attributes)
player?.prepare()
playerPrepared = true
m.setVolume(100f, 100f)
m.isLooping = false
isPlaying = true
m.start()
player?.setVolume(100f, 100f)
player?.isLooping = false
player?.start()
} catch (e: Exception) {
RxErrorHandler.reportError(e)
}
}
override fun onCompletion(mediaPlayer: MediaPlayer) {
isPlaying = false
}
}

View file

@ -70,7 +70,9 @@ constructor(
container.addView(createTextView(context, xp, HabiticaIconsHelper.imageOfExperience()))
}
if (hp != null && hp != 0.0) {
displayType = SnackbarDisplayType.FAILURE
if (hp < 0) {
displayType = SnackbarDisplayType.FAILURE
}
container.addView(createTextView(context, hp, HabiticaIconsHelper.imageOfHeartDarkBg()))
}
if (gold != null && gold != 0.0) {

View file

@ -1,5 +1,3 @@
package com.habitrpg.android.habitica.models
class IAPGift {
var uuid: String? = null
}
class IAPGift(var uuid: String? = null)

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.fragments.purchases.GiftBalanceGemsFragment
import com.habitrpg.android.habitica.ui.fragments.purchases.GiftPurchaseGemsFragment
@ -48,6 +49,7 @@ class GiftGemsActivity : BaseActivity() {
private var giftedUsername: String? = null
private var giftedUserID: String? = null
private var giftedMember: Member? = null
private var purchaseFragment: GiftPurchaseGemsFragment? = null
private var balanceFragment: GiftBalanceGemsFragment? = null
@ -89,6 +91,7 @@ class GiftGemsActivity : BaseActivity() {
compositeSubscription.add(
socialRepository.getMember(giftedUsername ?: giftedUserID).firstElement().subscribe(
{
giftedMember = it
giftedUserID = it.id
giftedUsername = it.username
purchaseFragment?.giftedMember = it
@ -139,6 +142,7 @@ class GiftGemsActivity : BaseActivity() {
fragment.setPurchaseHandler(purchaseHandler)
fragment.setupCheckout()
purchaseFragment = fragment
purchaseFragment?.giftedMember = giftedMember
fragment
} else {
val fragment = GiftBalanceGemsFragment()
@ -146,6 +150,7 @@ class GiftGemsActivity : BaseActivity() {
displayConfirmationDialog()
}
balanceFragment = fragment
balanceFragment?.giftedMember = giftedMember
fragment
}
}

View file

@ -185,11 +185,13 @@ class GiftSubscriptionActivity : BaseActivity() {
}
private fun purchaseSubscription(sku: Sku) {
if (giftedUserID?.isNotEmpty() != true) {
return
giftedUserID?.let { id ->
if (id.isEmpty()) {
return
}
HabiticaPurchaseVerifier.addGift(sku.id.code, id)
purchaseHandler?.purchaseNoRenewSubscription(sku)
}
HabiticaPurchaseVerifier.addGift(sku.id.code, giftedUserID)
purchaseHandler?.purchaseNoRenewSubscription(sku)
}
@Subscribe

View file

@ -35,7 +35,10 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
field = value
for (item in items) {
if (item.isHeader) {
notifyItemChanged(getVisibleItemPosition(item.identifier))
val visiblePosition = getVisibleItemPosition(item.identifier)
if (visiblePosition >= 0) {
notifyItemChanged(visiblePosition)
}
}
}
}
@ -70,7 +73,10 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
notifyItemInserted(items.size - 1)
} else {
items[position] = item
notifyItemChanged(getVisibleItemPosition(item.identifier))
val visiblePosition = getVisibleItemPosition(item.identifier)
if (visiblePosition in 0 until itemCount) {
notifyItemChanged(visiblePosition)
}
}
}

View file

@ -70,7 +70,7 @@ class ItemsFragment : BaseMainFragment<FragmentViewpagerBinding>() {
}
fragment.user = this@ItemsFragment.user
fragment.itemTypeText =
if (position == 4) getString(R.string.special_items)
if (position == 4 && isAdded) getString(R.string.special_items)
else getPageTitle(position)
return fragment

View file

@ -34,7 +34,7 @@ class TimePreferenceDialogFragment : PreferenceDialogFragmentCompat() {
}
override fun onCreateDialogView(context: Context?): View {
picker = TimePicker(getContext())
picker = TimePicker(context)
return picker
}

View file

@ -91,7 +91,9 @@ class GiftPurchaseGemsFragment : BaseFragment<FragmentGiftGemPurchaseBinding>()
}
private fun purchaseGems(identifier: String) {
HabiticaPurchaseVerifier.addGift(identifier, giftedMember?.id)
purchaseHandler?.purchaseGems(identifier)
giftedMember?.id?.let {
HabiticaPurchaseVerifier.addGift(identifier, it)
purchaseHandler?.purchaseGems(identifier)
}
}
}

View file

@ -21,6 +21,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Named
@ -57,7 +58,9 @@ class BugFixFragment : BaseMainFragment<FragmentSupportBugFixBinding>() {
compositeSubscription.add(
Completable.fromAction {
deviceInfo = context?.let { DeviceName.getDeviceInfo(it) }
}.subscribe()
}
.subscribeOn(Schedulers.io())
.subscribe()
)
binding?.reportBugButton?.setOnClickListener {

View file

@ -16,6 +16,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Named
@ -63,7 +64,9 @@ class SupportMainFragment : BaseMainFragment<FragmentSupportMainBinding>() {
compositeSubscription.add(
Completable.fromAction {
deviceInfo = context?.let { DeviceName.getDeviceInfo(it) }
}.subscribe()
}
.subscribeOn(Schedulers.io())
.subscribe()
)
binding?.resetTutorialButton?.setOnClickListener {