diff --git a/Habitica/build.gradle b/Habitica/build.gradle
index 40a08c857..9d83d954c 100644
--- a/Habitica/build.gradle
+++ b/Habitica/build.gradle
@@ -47,8 +47,8 @@ dependencies {
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//Dependency Injection
- implementation 'com.google.dagger:dagger:2.38'
- kapt 'com.google.dagger:dagger-compiler:2.38'
+ implementation 'com.google.dagger:dagger:2.39.1'
+ kapt 'com.google.dagger:dagger-compiler:2.39.1'
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
compileOnly 'com.github.pengrad:jdk9-deps:1.0'
//App Compatibility and Material Design
@@ -82,8 +82,8 @@ dependencies {
//Analytics
implementation 'com.amplitude:android-sdk:2.30.0'
// Image Management Library
- implementation("io.coil-kt:coil:1.2.2")
- implementation("io.coil-kt:coil-gif:1.2.2")
+ implementation("io.coil-kt:coil:1.4.0")
+ implementation("io.coil-kt:coil-gif:1.4.0")
//Tests
testImplementation 'io.kotest:kotest-runner-junit5:4.6.2'
@@ -116,10 +116,12 @@ dependencies {
implementation 'com.nex3z:flow-layout:1.2.2'
implementation 'androidx.core:core-ktx:1.6.0'
- implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
+ implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
@@ -153,8 +155,8 @@ 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 3052
- versionName "3.4"
+ versionCode 3070
+ versionName "3.4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/Habitica/res/layout/quest_menu_view.xml b/Habitica/res/layout/quest_menu_view.xml
index d519ec75e..a156a763f 100644
--- a/Habitica/res/layout/quest_menu_view.xml
+++ b/Habitica/res/layout/quest_menu_view.xml
@@ -36,9 +36,8 @@
android:textSize="12sp"/>
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/values-en-rGB/strings.xml b/Habitica/res/values-en-rGB/strings.xml
index ef916d1ee..daad320c5 100644
--- a/Habitica/res/values-en-rGB/strings.xml
+++ b/Habitica/res/values-en-rGB/strings.xml
@@ -1098,5 +1098,5 @@
The Gem Sale is back to haunt the very end of this year’s Fall Gala! This is one last chance to get more Gems than ever, so stock up while it lasts!
Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
View Gem Bundles
- The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before.
-
\ No newline at end of file
+ The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.
+
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 64a224eea..784016d1d 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1114,7 +1114,7 @@
How it works
Limitations
Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
- The Fall Gala is in full swing so we thought it was the perfect time to introduce our first ever Gem Sale! Now you will get more Gems with each purchase than ever before.
+ The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.
View Gem Bundles
Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
This promotion only applies during the limited time event. This event starts on %s (12:00 UTC) and will end %s (00:00 UTC). The promo offer is only available when buying Gems for yourself.
@@ -1183,4 +1183,5 @@
Terms of Service
%.01f dmg pending
%s remaining
+ Sale ends in %s
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 216c0d5a2..27f27e7bc 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -6,9 +6,11 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
+import android.content.res.Configuration
import android.content.res.Resources
import android.database.DatabaseErrorHandler
import android.database.sqlite.SQLiteDatabase
+import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
@@ -31,6 +33,7 @@ import com.habitrpg.android.habitica.api.HostConfig
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
+import com.habitrpg.android.habitica.helpers.LanguageHelper
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.modules.UserModule
@@ -46,6 +49,7 @@ import org.solovyev.android.checkout.Billing
import org.solovyev.android.checkout.Cache
import org.solovyev.android.checkout.Checkout
import org.solovyev.android.checkout.PurchaseVerifier
+import java.util.Locale
import javax.inject.Inject
// contains all HabiticaApplicationLogic except dagger componentInitialisation
@@ -78,6 +82,7 @@ abstract class HabiticaBaseApplication : Application() {
super.onCreate()
setupRealm()
setupDagger()
+ setLocale()
setupRemoteConfig()
setupNotifications()
createBillingAndCheckout()
@@ -117,6 +122,21 @@ abstract class HabiticaBaseApplication : Application() {
checkIfNewVersion()
}
+ private fun setLocale() {
+ val resources = resources
+ val configuration: Configuration = resources.configuration
+ val languageHelper = LanguageHelper(sharedPrefs.getString("language", "en"))
+ if (if (SDK_INT >= Build.VERSION_CODES.N) {
+ configuration.locales.isEmpty || configuration.locales[0] != languageHelper.locale
+ } else {
+ configuration.locale != languageHelper.locale
+ }
+ ) {
+ configuration.setLocale(languageHelper.locale)
+ resources.updateConfiguration(configuration, null)
+ }
+ }
+
protected open fun setupRealm() {
Realm.init(this)
val builder = RealmConfiguration.Builder()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt
index f96c84d9d..5d062fcb8 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.kt
@@ -2,6 +2,7 @@ package com.habitrpg.android.habitica
import android.content.Context
import android.content.SharedPreferences
+import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
@@ -26,32 +27,35 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
private val context: Context
override fun doVerify(purchases: List, requestListener: RequestListener>) {
val verifiedPurchases: MutableList = ArrayList(purchases.size)
+ val allPurchases = purchases.toMutableList()
for (purchase in purchases) {
if (purchasedOrderList.contains(purchase.orderId)) {
verifiedPurchases.add(purchase)
- requestListener.onSuccess(verifiedPurchases)
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
} else {
when {
PurchaseTypes.allGemTypes.contains(purchase.sku) -> {
val validationRequest = buildValidationRequest(purchase)
apiClient.validatePurchase(validationRequest).subscribe({
purchasedOrderList.add(purchase.orderId)
- requestListener.onSuccess(verifiedPurchases)
+ verifiedPurchases.add(purchase)
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
val giftedID = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
}) { throwable: Throwable ->
- handleError(throwable, purchase, requestListener, verifiedPurchases)
+ handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
}
}
PurchaseTypes.allSubscriptionNoRenewTypes.contains(purchase.sku) -> {
val validationRequest = buildValidationRequest(purchase)
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
purchasedOrderList.add(purchase.orderId)
- requestListener.onSuccess(verifiedPurchases)
+ verifiedPurchases.add(purchase)
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
val giftedID = removeGift(purchase.sku)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase, giftedID))
}) { throwable: Throwable ->
- handleError(throwable, purchase, requestListener, verifiedPurchases)
+ handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
}
}
PurchaseTypes.allSubscriptionTypes.contains(purchase.sku) -> {
@@ -63,20 +67,35 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
apiClient.validateSubscription(validationRequest).subscribe({
purchasedOrderList.add(purchase.orderId)
verifiedPurchases.add(purchase)
- requestListener.onSuccess(verifiedPurchases)
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
FirebaseAnalytics.getInstance(context).logEvent("user_subscribed", null)
EventBus.getDefault().post(UserSubscribedEvent())
}) { throwable: Throwable ->
- handleError(throwable, purchase, requestListener, verifiedPurchases)
+ handleError(throwable, purchase, allPurchases, requestListener, verifiedPurchases)
}
}
}
}
}
- val edit = preferences?.edit()
- edit?.putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList)
- edit?.apply()
- savePendingGifts()
+ preferences?.edit {
+ putStringSet(PURCHASED_PRODUCTS_KEY, purchasedOrderList)
+ }
+ }
+
+ private fun processedPurchase(
+ purchase: Purchase,
+ allPurchases: MutableList,
+ verifiedPurchases: MutableList,
+ requestListener: RequestListener>
+ ) {
+ allPurchases.remove(purchase)
+ if (allPurchases.isEmpty()) {
+ if (verifiedPurchases.isEmpty()) {
+ requestListener.onError(ResponseCodes.ERROR, Exception())
+ } else {
+ requestListener.onSuccess(verifiedPurchases)
+ }
+ }
}
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
@@ -92,13 +111,17 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
return validationRequest
}
- private fun handleError(throwable: Throwable, purchase: Purchase, requestListener: RequestListener>, verifiedPurchases: MutableList) {
+ private fun handleError(throwable: Throwable, purchase: Purchase,
+ allPurchases: MutableList,
+ requestListener: RequestListener>,
+ verifiedPurchases: MutableList) {
(throwable as? HttpException)?.let { error ->
if (error.code() == 401) {
val res = apiClient.getErrorResponse(throwable)
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
purchasedOrderList.add(purchase.orderId)
- requestListener.onSuccess(verifiedPurchases)
+ verifiedPurchases.add(purchase)
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
EventBus.getDefault().post(ConsumablePurchasedEvent(purchase))
removeGift(purchase.sku)
return
@@ -106,7 +129,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
}
}
FirebaseCrashlytics.getInstance().recordException(throwable)
- requestListener.onError(ResponseCodes.ERROR, Exception())
+ processedPurchase(purchase, allPurchases, verifiedPurchases, requestListener)
}
private fun loadPendingGifts(): MutableMap {
@@ -131,6 +154,7 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS"
private var pendingGifts: MutableMap = HashMap()
private var preferences: SharedPreferences? = null
+
fun addGift(sku: String?, userID: String?) {
pendingGifts[sku] = userID
savePendingGifts()
@@ -145,10 +169,9 @@ class HabiticaPurchaseVerifier(context: Context, apiClient: ApiClient) : BasePur
private fun savePendingGifts() {
val jsonObject = JSONObject(pendingGifts as Map<*, *>)
val jsonString = jsonObject.toString()
- val editor = preferences?.edit()
- editor?.remove(PENDING_GIFTS_KEY)
- editor?.putString(PENDING_GIFTS_KEY, jsonString)
- editor?.apply()
+ preferences?.edit {
+ putString(PENDING_GIFTS_KEY, jsonString)
+ }
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt
index 795d5463c..4c9a92965 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt
@@ -218,7 +218,7 @@ interface ApiService {
fun seenMessages(@Path("gid") groupId: String): Flowable>
@POST("groups/{gid}/invite")
- fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable>
+ fun inviteToGroup(@Path("gid") groupId: String, @Body inviteData: Map): Flowable>>
@POST("groups/{gid}/reject-invite")
fun rejectGroupInvite(@Path("gid") groupId: String): Flowable>
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
index f62177ff5..c24273044 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
@@ -103,6 +103,7 @@ import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment;
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
+import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog;
import com.habitrpg.android.habitica.ui.views.social.ChatBarView;
@@ -346,4 +347,6 @@ public interface UserComponent {
void inject(@NotNull PromoWebFragment promoWebFragment);
void inject(@NotNull ItemDialogFragment itemDialogFragment);
+
+ void inject(@NotNull EquipmentOverviewViewModel equipmentOverviewViewModel);
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
index 88586a755..c3a7eed58 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
@@ -153,7 +153,7 @@ interface ApiClient {
fun seenMessages(groupId: String): Flowable
- fun inviteToGroup(groupId: String, inviteData: Map): Flowable
+ fun inviteToGroup(groupId: String, inviteData: Map): Flowable>
fun rejectGroupInvite(groupId: String): Flowable
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
index 61da77a81..b0c0ef49a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
@@ -51,7 +51,7 @@ interface SocialRepository : BaseRepository {
fun getGroupMembers(id: String): Flowable>
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable>
- fun inviteToGroup(id: String, inviteData: Map): Flowable
+ fun inviteToGroup(id: String, inviteData: Map): Flowable>
fun getMember(userId: String?): Flowable
fun getMemberWithUsername(username: String?): Flowable
@@ -60,6 +60,8 @@ interface SocialRepository : BaseRepository {
fun markPrivateMessagesRead(user: User?): Flowable
+ fun markSomePrivateMessagesAsRead(user: User?, messages: List)
+
fun transferGroupOwnership(groupID: String, userID: String): Flowable
fun removeMemberFromGroup(groupID: String, userID: String): Flowable>
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
index c6d60fcd5..942b46c64 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
@@ -12,7 +12,6 @@ import com.habitrpg.android.habitica.api.GSonFactoryCreator
import com.habitrpg.android.habitica.api.HostConfig
import com.habitrpg.android.habitica.api.Server
import com.habitrpg.android.habitica.data.ApiClient
-import com.habitrpg.android.habitica.events.ConsumablePurchasedEvent
import com.habitrpg.android.habitica.events.ShowConnectionProblemEvent
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.models.*
@@ -554,7 +553,7 @@ class ApiClientImpl // private OnHabitsAPIResult mResultListener;
return apiService.seenMessages(groupId).compose(configureApiCallObserver())
}
- override fun inviteToGroup(groupId: String, inviteData: Map): Flowable {
+ override fun inviteToGroup(groupId: String, inviteData: Map): Flowable> {
return apiService.inviteToGroup(groupId, inviteData).compose(configureApiCallObserver())
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
index c58fabf92..c726a01e9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
@@ -241,7 +241,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
.doOnNext { members -> localRepository.saveGroupMembers(id, members) }
}
- override fun inviteToGroup(id: String, inviteData: Map): Flowable = apiClient.inviteToGroup(id, inviteData)
+ override fun inviteToGroup(id: String, inviteData: Map): Flowable> = apiClient.inviteToGroup(id, inviteData)
override fun getMember(userId: String?): Flowable {
return if (userId == null) {
@@ -264,12 +264,31 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
override fun markPrivateMessagesRead(user: User?): Flowable {
+ if (user?.isManaged == true) {
+ localRepository.modify(user) {
+ it.inbox?.hasUserSeenInbox = true
+ }
+ }
return apiClient.markPrivateMessagesRead()
- .doOnNext {
- if (user?.isManaged == true) {
- localRepository.modify(user) { it.inbox?.newMessages = 0 }
+ }
+
+ override fun markSomePrivateMessagesAsRead(user: User?, messages: List) {
+ if (user?.isManaged == true) {
+ val numOfUnseenMessages = messages.count { !it.isSeen }
+ localRepository.modify(user) {
+ val numOfNewMessagesFromInbox = it.inbox?.newMessages ?: 0
+ if (numOfNewMessagesFromInbox > numOfUnseenMessages) {
+ it.inbox?.newMessages = numOfNewMessagesFromInbox - numOfUnseenMessages
+ } else {
+ it.inbox?.newMessages = 0
}
}
+ }
+ for (message in messages.filter { it.isManaged && !it.isSeen }) {
+ localRepository.modify(message) {
+ it.isSeen = true
+ }
+ }
}
override fun getUserGroups(type: String?): Flowable> = localRepository.getUserGroups(userID, type)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
index efbced2a6..07329b047 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
@@ -64,6 +64,13 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
override fun saveInboxMessages(userID: String, recipientID: String, messages: List, page: Int) {
messages.forEach { it.userID = userID }
+ for (message in messages) {
+ val existingMessage = realm.where(ChatMessage::class.java)
+ .equalTo("id", message.id)
+ .findAll()
+ .firstOrNull()
+ message.isSeen = existingMessage != null
+ }
save(messages)
if (page != 0) return
val existingMessages = realm.where(ChatMessage::class.java).equalTo("isInboxMessage", true).equalTo("uuid", recipientID).findAll()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt
index 28e0e317d..8f1e93c8c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTagLocalRepository.kt
@@ -8,11 +8,13 @@ import io.realm.Realm
class RealmTagLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TagLocalRepository {
override fun deleteTag(tagID: String) {
- val tag = realm.where(Tag::class.java).equalTo("id", tagID).findFirst()
- executeTransaction { tag?.deleteFromRealm() }
+ val tags = realm.where(Tag::class.java).equalTo("id", tagID).findAll()
+ executeTransaction { tags.deleteAllFromRealm() }
}
override fun getTags(userId: String): Flowable> {
- return RxJavaBridge.toV3Flowable(realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable())
+ return RxJavaBridge.toV3Flowable(
+ realm.where(Tag::class.java).equalTo("userId", userId).findAll().asFlowable()
+ )
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt
index 3d9a8a4e7..455c2b00d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt
@@ -90,6 +90,17 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
}
}
executeTransaction { realm1 -> realm1.insertOrUpdate(user) }
+ removeOldTags(user.id ?: "", user.tags)
+ }
+
+ private fun removeOldTags(userId: String, onlineTags: List) {
+ val tags = realm.where(Tag::class.java).equalTo("userId", userId).findAll().createSnapshot()
+ val tagsToDelete = tags.filterNot { onlineTags.contains(it) }
+ executeTransaction {
+ for (tag in tagsToDelete) {
+ tag.deleteFromRealm()
+ }
+ }
}
override fun saveMessages(messages: List) {
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 8965115dd..8f0195f5d 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
@@ -126,6 +126,9 @@ class AppConfigManager(contentRepository: ContentRepository?) {
if (promo == null && remoteConfig.getString("activePromo").isNotBlank()) {
promo = getHabiticaPromotionFromKey(remoteConfig.getString("activePromo"), null, null)
}
+ if (promo?.isActive != true) {
+ return null
+ }
if (promo is HabiticaWebPromotion) {
promo.url = surveyURL()
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
index 45a415a1e..65fb1736b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
@@ -4,6 +4,8 @@ import android.app.Activity
import android.content.Intent
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.proxy.AnalyticsManager
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.solovyev.android.checkout.*
import java.util.*
import kotlin.coroutines.resume
@@ -74,16 +76,18 @@ open class PurchaseHandler(activity: Activity, val analyticsManager: AnalyticsMa
return purchases.skus.firstOrNull()
}
- private suspend fun loadInventory(type: String, skus: List): Inventory.Products? = suspendCoroutine { cont ->
- val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus)
- try {
- inventory?.load(request) {
- cont.resume(it)
+ private suspend fun loadInventory(type: String, skus: List): Inventory.Products? = withContext(Dispatchers.Main) {
+ suspendCoroutine { cont ->
+ val request = Inventory.Request.create().loadAllPurchases().loadSkus(type, skus)
+ try {
+ inventory?.load(request) {
+ cont.resume(it)
+ }
+ } catch (e: NullPointerException) {
+ cont.resumeWithException(e)
}
- } catch (e: NullPointerException) {
- cont.resumeWithException(e)
+ if (inventory == null) cont.resume(null)
}
- if (inventory == null) cont.resume(null)
}
fun purchaseSubscription(sku: Sku, onSuccess: (() -> Unit)) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt
index 940ff3765..d4387f280 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.kt
@@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.helpers
import com.habitrpg.android.habitica.HabiticaBaseApplication
-import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject
@@ -17,9 +16,10 @@ class SoundManager {
HabiticaBaseApplication.userComponent?.inject(this)
}
- fun preloadAllFiles(): Maybe> {
+ fun preloadAllFiles() {
+ loadedSoundFiles.clear()
if (soundTheme == SoundThemeOff) {
- return Maybe.empty()
+ return
}
val soundFiles = ArrayList()
@@ -33,7 +33,8 @@ class SoundManager {
soundFiles.add(SoundFile(soundTheme, SoundPlusHabit))
soundFiles.add(SoundFile(soundTheme, SoundReward))
soundFiles.add(SoundFile(soundTheme, SoundTodo))
- return soundFileLoader.download(soundFiles).toMaybe()
+ soundFileLoader.download(soundFiles)
+ .subscribe({}, RxErrorHandler.handleEmptyError())
}
fun loadAndPlayAudio(type: String) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
index 2b80cb16f..2b4db0b6e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GroupActivityNotification.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.helpers.notifications
+import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
@@ -40,6 +41,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
oldMessages.add(data)
return super.configureNotificationBuilder(data)
.setStyle(style)
+ .setCategory(Notification.CATEGORY_MESSAGE)
.setExtras(bundleOf(Pair("messages", bundleOf(Pair("messages", oldMessages)))))
}
@@ -55,8 +57,8 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
)
}
- override fun setNotificationActions(data: Map) {
- super.setNotificationActions(data)
+ override fun setNotificationActions(notificationId: Int, data: Map) {
+ super.setNotificationActions(notificationId, data)
val groupID = data["groupID"] ?: return
val actionName = context.getString(R.string.group_message_reply)
@@ -68,6 +70,7 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
intent.action = actionName
intent.putExtra("groupID", groupID)
+ intent.putExtra("NOTIFICATION_ID", notificationId)
val replyPendingIntent: PendingIntent =
PendingIntent.getBroadcast(
context, groupID.hashCode(),
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
index e9e3f2ecc..80c5e0421 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/GuildInviteLocalNotification.kt
@@ -16,16 +16,18 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
intent.putExtra("groupID", data?.get("groupID"))
}
- override fun setNotificationActions(data: Map) {
- super.setNotificationActions(data)
+ override fun setNotificationActions(notificationId: Int, data: Map) {
+ super.setNotificationActions(notificationId, data)
val res = context.resources
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
acceptInviteIntent.action = res.getString(R.string.accept_guild_invite)
- acceptInviteIntent.putExtra("groupID", this.data?.get("groupID"))
+ val groupID = data.get("groupID")
+ acceptInviteIntent.putExtra("groupID", groupID)
+ acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentAccept = PendingIntent.getBroadcast(
context,
- 3000,
+ groupID.hashCode(),
acceptInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
@@ -33,10 +35,11 @@ class GuildInviteLocalNotification(context: Context, identifier: String?) : Habi
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
rejectInviteIntent.action = res.getString(R.string.reject_guild_invite)
- rejectInviteIntent.putExtra("groupID", this.data?.get("groupID"))
+ rejectInviteIntent.putExtra("groupID", groupID)
+ acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentReject = PendingIntent.getBroadcast(
context,
- 2000,
+ groupID.hashCode() + 1,
rejectInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
index b85a6369e..f44f7d0b8 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/HabiticaLocalNotification.kt
@@ -44,19 +44,21 @@ abstract class HabiticaLocalNotification(protected var context: Context, protect
notificationBuilder = notificationBuilder.setContentText(message)
}
- this.setNotificationActions(data)
+ val notificationId = getNotificationID(data)
+ this.setNotificationActions(notificationId, data)
val notificationManager = NotificationManagerCompat.from(context)
- notificationManager.notify(getNotificationID(data), notificationBuilder.build())
+ notificationManager.notify(notificationId, notificationBuilder.build())
}
fun setExtras(data: Map) {
this.data = data
}
- protected open fun setNotificationActions(data: Map) {
+ protected open fun setNotificationActions(notificationId: Int, data: Map) {
val intent = Intent(context, MainActivity::class.java)
configureMainIntent(intent)
+ intent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntent = PendingIntent.getActivity(
context,
3000,
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
index 14ddb6ebd..21c0cc3f7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PartyInviteLocalNotification.kt
@@ -11,16 +11,18 @@ import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver
*/
class PartyInviteLocalNotification(context: Context, identifier: String?) : HabiticaLocalNotification(context, identifier) {
- override fun setNotificationActions(data: Map) {
- super.setNotificationActions(data)
+ override fun setNotificationActions(notificationId: Int, data: Map) {
+ super.setNotificationActions(notificationId, data)
val res = context.resources
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
acceptInviteIntent.action = res.getString(R.string.accept_party_invite)
- acceptInviteIntent.putExtra("groupID", this.data?.get("groupID"))
+ val groupID = data.get("groupID")
+ acceptInviteIntent.putExtra("groupID", groupID)
+ acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentAccept = PendingIntent.getBroadcast(
context,
- 3000,
+ groupID.hashCode(),
acceptInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
@@ -28,10 +30,11 @@ class PartyInviteLocalNotification(context: Context, identifier: String?) : Habi
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
rejectInviteIntent.action = res.getString(R.string.reject_party_invite)
- rejectInviteIntent.putExtra("groupID", this.data?.get("groupID"))
+ rejectInviteIntent.putExtra("groupID", groupID)
+ rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentReject = PendingIntent.getBroadcast(
context,
- 2000,
+ groupID.hashCode() + 1,
rejectInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
index 2f347a31a..1c3bb5906 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/QuestInviteLocalNotification.kt
@@ -15,15 +15,16 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
return 1000
}
- override fun setNotificationActions(data: Map) {
- super.setNotificationActions(data)
+ override fun setNotificationActions(notificationId: Int, data: Map) {
+ super.setNotificationActions(notificationId, data)
val res = context.resources
val acceptInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
acceptInviteIntent.action = res.getString(R.string.accept_quest_invite)
+ acceptInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentAccept = PendingIntent.getBroadcast(
context,
- 3000,
+ 3001,
acceptInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
@@ -31,9 +32,10 @@ class QuestInviteLocalNotification(context: Context, identifier: String?) : Habi
val rejectInviteIntent = Intent(context, LocalNotificationActionReceiver::class.java)
rejectInviteIntent.action = res.getString(R.string.reject_quest_invite)
+ rejectInviteIntent.putExtra("NOTIFICATION_ID", notificationId)
val pendingIntentReject = PendingIntent.getBroadcast(
context,
- 2000,
+ 2001,
rejectInviteIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
index 1f51863f9..8b8502254 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/ReceivedPrivateMessageLocalNotification.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.helpers.notifications
+import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
@@ -36,6 +37,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
}
notification = notification
.setContentTitle(notificationTitle)
+ .setCategory(Notification.CATEGORY_MESSAGE)
.setStyle(style)
title = null
} else {
@@ -48,8 +50,8 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
return data["senderName"].hashCode()
}
- override fun setNotificationActions(data: Map) {
- super.setNotificationActions(data)
+ override fun setNotificationActions(notificationId: Int, data: Map) {
+ super.setNotificationActions(notificationId, data)
val senderID = data["replyTo"] ?: return
val actionName = context.getString(R.string.inbox_message_reply)
@@ -61,6 +63,7 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
val intent = Intent(context, LocalNotificationActionReceiver::class.java)
intent.action = actionName
intent.putExtra("senderID", senderID)
+ intent.putExtra("NOTIFICATION_ID", notificationId)
val replyPendingIntent: PendingIntent =
PendingIntent.getBroadcast(
context, senderID.hashCode(),
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt
index 5187fb51a..7145fe98b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/NotifyUserUseCase.kt
@@ -109,28 +109,28 @@ constructor(
return textView
}
- fun getNotificationAndAddStatsToUserAsText(xp: Double, hp: Double, gold: Double, mp: Double): Pair {
+ fun getNotificationAndAddStatsToUserAsText(xp: Double?, hp: Double?, gold: Double?, mp: Double?): Pair {
val builder = SpannableStringBuilder()
var displayType = SnackbarDisplayType.NORMAL
- if (xp > 0) {
- builder.append(" + ").append(xp.round(2).toString()).append(" Exp")
+ if ((xp ?: 0.0) > 0) {
+ builder.append(" + ").append(xp?.round(2).toString()).append(" Exp")
}
if (hp != 0.0) {
displayType = SnackbarDisplayType.FAILURE
- builder.append(" - ").append(abs(hp.round(2)).toString()).append(" Health")
+ builder.append(" - ").append(abs(hp?.round(2) ?: 0.0).toString()).append(" Health")
}
if (gold != 0.0) {
- if (gold > 0) {
- builder.append(" + ").append(gold.round(2).toString())
- } else if (gold < 0) {
+ if ((gold ?: 0.0) > 0) {
+ builder.append(" + ").append(gold?.round(2).toString())
+ } else if ((gold ?: 0.0) < 0) {
displayType = SnackbarDisplayType.FAILURE
- builder.append(" - ").append(abs(gold.round(2)).toString())
+ builder.append(" - ").append(abs(gold?.round(2) ?: 0.0).toString())
}
builder.append(" Gold")
}
- if (mp > 0) {
- builder.append(" + ").append(mp.round(2).toString()).append(" Mana")
+ if ((mp ?: 0.0) > 0) {
+ builder.append(" + ").append(mp?.round(2).toString()).append(" Mana")
}
return Pair(builder, displayType)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt
index 0c145f841..149d4362c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/HabiticaPromotion.kt
@@ -17,6 +17,11 @@ enum class PromoType {
}
abstract class HabiticaPromotion {
+ val isActive: Boolean
+ get() {
+ val now = Date()
+ return startDate.before(now) && endDate.after(now)
+ }
abstract val identifier: String
abstract val promoType: PromoType
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt
index a73d4990d..f7678d441 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/ChatMessage.kt
@@ -58,6 +58,8 @@ open class ChatMessage : RealmObject(), BaseMainObject {
val formattedUsername: String?
get() = if (username != null) "@$username" else null
+ var isSeen: Boolean = true
+
fun userLikesMessage(userId: String?): Boolean {
return likes?.any { userId == it.id } ?: false
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt
index 51620f7d2..9aacccd7d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Inbox.kt
@@ -10,4 +10,5 @@ open class Inbox : RealmObject(), BaseObject {
var optOut: Boolean = false
var blocks: RealmList = RealmList()
var newMessages: Int = 0
+ var hasUserSeenInbox: Boolean = false
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt
index b29ce5103..5bd4483ff 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/LocalNotificationActionReceiver.kt
@@ -4,6 +4,9 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.widget.Toast
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import com.habitrpg.android.habitica.HabiticaBaseApplication
@@ -13,37 +16,44 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
import com.habitrpg.android.habitica.models.user.User
import javax.inject.Inject
class LocalNotificationActionReceiver : BroadcastReceiver() {
@Inject
lateinit var userRepository: UserRepository
+
@Inject
lateinit var socialRepository: SocialRepository
+
@Inject
lateinit var taskRepository: TaskRepository
+
@Inject
lateinit var apiClient: ApiClient
private var user: User? = null
- private var groupID: String? = null
- private var senderID: String? = null
+ private val groupID: String?
+ get() = intent?.extras?.getString("groupID")
+ private val senderID: String?
+ get() = intent?.extras?.getString("senderID")
+ private val taskID: String?
+ get() = intent?.extras?.getString("taskID")
private var context: Context? = null
private var intent: Intent? = null
override fun onReceive(context: Context, intent: Intent) {
HabiticaBaseApplication.userComponent?.inject(this)
this.intent = intent
- groupID = intent.extras?.getString("groupID")
- senderID = intent.extras?.getString("senderID")
this.context = context
handleLocalNotificationAction(intent.action)
}
private fun handleLocalNotificationAction(action: String?) {
- val notificationManager = this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
- notificationManager?.cancelAll()
+ val notificationManager =
+ this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
+ notificationManager?.cancel(intent?.extras?.getInt("NOTIFICATION_ID") ?: -1)
when (action) {
context?.getString(R.string.accept_party_invite) -> {
groupID?.let {
@@ -52,7 +62,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
}
context?.getString(R.string.reject_party_invite) -> {
groupID?.let {
- socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError())
+ socialRepository.rejectGroupInvite(it)
+ .subscribe({ }, RxErrorHandler.handleEmptyError())
}
}
context?.getString(R.string.accept_quest_invite) -> {
@@ -68,7 +79,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
}
context?.getString(R.string.reject_guild_invite) -> {
groupID?.let {
- socialRepository.rejectGroupInvite(it).subscribe({ }, RxErrorHandler.handleEmptyError())
+ socialRepository.rejectGroupInvite(it)
+ .subscribe({ }, RxErrorHandler.handleEmptyError())
}
}
context?.getString(R.string.group_message_reply) -> {
@@ -76,7 +88,9 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
getMessageText(context?.getString(R.string.group_message_reply))?.let { message ->
socialRepository.postGroupChat(it, message).subscribe(
{
- context?.let { c -> NotificationManagerCompat.from(c).cancel(it.hashCode()) }
+ context?.let { c ->
+ NotificationManagerCompat.from(c).cancel(it.hashCode())
+ }
},
RxErrorHandler.handleEmptyError()
)
@@ -86,19 +100,33 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
context?.getString(R.string.inbox_message_reply) -> {
senderID?.let {
getMessageText(context?.getString(R.string.inbox_message_reply))?.let { message ->
- socialRepository.postPrivateMessage(it, message).subscribe({ }, RxErrorHandler.handleEmptyError())
+ socialRepository.postPrivateMessage(it, message)
+ .subscribe({ }, RxErrorHandler.handleEmptyError())
}
}
}
context?.getString(R.string.complete_task_action) -> {
- intent?.extras?.getString("taskID")?.let {
+ taskID?.let {
taskRepository.taskChecked(null, it, up = true, force = false) {
- }.subscribe({}, RxErrorHandler.handleEmptyError())
+ }.subscribe({
+ val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(
+ it?.experienceDelta,
+ it?.healthDelta,
+ it?.goldDelta,
+ it?.manaDelta
+ )
+ showToast(pair.first)
+ }, RxErrorHandler.handleEmptyError())
}
}
}
}
+ private fun showToast(text: Spannable) {
+ val toast = Toast.makeText(context, text, Toast.LENGTH_LONG)
+ toast.show()
+ }
+
private fun getMessageText(key: String?): String? {
return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(key)?.toString()
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
index 85743da84..2425fd7f9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.kt
@@ -1,10 +1,12 @@
package com.habitrpg.android.habitica.receivers
+import android.app.Notification
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
+import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.habitrpg.android.habitica.HabiticaBaseApplication
@@ -62,21 +64,29 @@ class TaskReceiver : BroadcastReceiver() {
val pendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), intent, 0)
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
- val notificationBuilder = NotificationCompat.Builder(context, "default")
+ var notificationBuilder = NotificationCompat.Builder(context, "default")
.setSmallIcon(R.drawable.ic_gryphon_white)
.setContentTitle(task.text)
+ .setStyle(NotificationCompat.BigTextStyle()
+ .bigText(task.notes))
.setPriority(NotificationCompat.PRIORITY_MAX)
.setSound(soundUri)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ notificationBuilder = notificationBuilder.setCategory(Notification.CATEGORY_REMINDER)
+ }
+
if (task.type == Task.TYPE_DAILY || task.type == Task.TYPE_TODO) {
- val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java)
- completeIntent.action = context.getString(R.string.complete_task_action)
- completeIntent.putExtra("taskID", task.id)
+ val completeIntent = Intent(context, LocalNotificationActionReceiver::class.java).apply {
+ action = context.getString(R.string.complete_task_action)
+ putExtra("taskID", task.id)
+ putExtra("NOTIFICATION_ID", task.id.hashCode())
+ }
val pendingIntentComplete = PendingIntent.getBroadcast(
context,
- 3000,
+ task.id.hashCode(),
completeIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt
index 5572b178f..b3166efa8 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt
@@ -12,6 +12,7 @@ import androidx.core.view.marginStart
import androidx.core.view.marginTop
import coil.clear
import coil.load
+import coil.target.ImageViewTarget
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.dpToPx
@@ -143,17 +144,15 @@ class AvatarView : FrameLayout {
imageView.load(DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename(layerName)) {
allowHardware(false)
- target(
- {},
- {
+ target(object : ImageViewTarget(imageView) {
+ override fun onError(error: Drawable?) {
+ super.onError(error)
onLayerComplete()
- },
- {
- if (imageView.tag != layerName) {
- return@target
- }
- val bounds = getLayerBounds(layerKey, layerName, it)
- imageView.setImageDrawable(it)
+ }
+
+ override fun onSuccess(result: Drawable) {
+ super.onSuccess(result)
+ val bounds = getLayerBounds(layerKey, layerName, result)
imageView.imageMatrix = avatarMatrix
val layoutParams = imageView.layoutParams as? LayoutParams
layoutParams?.topMargin = bounds.top
@@ -163,7 +162,7 @@ class AvatarView : FrameLayout {
imageView.layoutParams = layoutParams
onLayerComplete()
}
- )
+ })
}
}
while (i < (imageViewHolder.size)) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
index 62a584a0d..335d7ff5f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
+import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@@ -61,7 +62,7 @@ abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en"))
- resources.forceLocale(languageHelper.locale)
+ resources.forceLocale(this, languageHelper.locale)
delegate.localNightMode = when (sharedPreferences.getString("theme_mode", "system")) {
"light" -> AppCompatDelegate.MODE_NIGHT_NO
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
@@ -77,6 +78,13 @@ abstract class BaseActivity : AppCompatActivity() {
compositeSubscription = CompositeDisposable()
}
+ override fun onRestart() {
+ super.onRestart()
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
+ val languageHelper = LanguageHelper(sharedPreferences.getString("language", "en"))
+ resources.forceLocale(this, languageHelper.locale)
+ }
+
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
@@ -222,9 +230,12 @@ abstract class BaseActivity : AppCompatActivity() {
}
}
-private fun Resources.forceLocale(locale: Locale) {
+private fun Resources.forceLocale(activity: BaseActivity, locale: Locale) {
Locale.setDefault(locale)
val configuration = Configuration()
configuration.setLocale(locale)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ activity.createConfigurationContext(configuration)
+ }
updateConfiguration(configuration, displayMetrics)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index d7092d3d6..a1f9e4625 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -847,7 +847,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
}
@Subscribe
- fun showWonAchievementDialog(event: ShowWonChallengeDialog) {
+ fun showWonChallengeDialog(event: ShowWonChallengeDialog) {
retrieveUser(true)
lifecycleScope.launch(context = Dispatchers.Main) {
val dialog = WonChallengeDialog(this@MainActivity)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt
index 8cfa2adde..b468fe561 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt
@@ -20,7 +20,7 @@ class PrefsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceStart
setupToolbar(findViewById(R.id.toolbar))
supportFragmentManager.beginTransaction()
- .add(R.id.fragment_container, PreferencesFragment())
+ .replace(R.id.fragment_container, PreferencesFragment())
.commit()
}
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 485918e97..a1b694030 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
@@ -16,6 +16,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.GravityCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.SimpleItemAnimator
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ContentRepository
@@ -28,12 +29,14 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.WorldState
+import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
import com.habitrpg.android.habitica.models.promotions.PromoType
import com.habitrpg.android.habitica.models.social.Group
+import com.habitrpg.android.habitica.models.user.Inbox
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.NotificationsActivity
@@ -98,6 +101,7 @@ class NavigationDrawerFragment : DialogFragment() {
private fun updateQuestDisplay() {
val quest = this.quest
val questContent = this.questContent
+ return
if (quest == null || questContent == null || !quest.active) {
binding?.questMenuView?.visibility = View.GONE
context?.let {
@@ -125,20 +129,6 @@ class NavigationDrawerFragment : DialogFragment() {
}
binding?.questMenuView?.setBackgroundColor(context?.getThemeColor(R.attr.colorPrimaryDark) ?: 0)
- /* Reenable this once the boss art can be displayed correctly.
-
- val preferences = context?.getSharedPreferences("collapsible_sections", 0)
- if (preferences?.getBoolean("boss_art_collapsed", false) == true) {
- questMenuView.hideBossArt()
- } else {
- questMenuView.showBossArt()
- }*/
- //binding?.questMenuView?.hideBossArt()
-
- /*getItemWithIdentifier(SIDEBAR_TAVERN)?.let { tavern ->
- tavern.subtitle = context?.getString(R.string.active_world_boss)
- adapter.updateItem(tavern)
- }*/
binding?.questMenuView?.setOnClickListener {
setSelection(R.id.partyFragment)
/*val context = this.context
@@ -181,6 +171,7 @@ class NavigationDrawerFragment : DialogFragment() {
binding?.recyclerView?.adapter = adapter
binding?.recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
+ (binding?.recyclerView?.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
initializeMenuItems()
subscriptions?.add(
@@ -237,12 +228,12 @@ class NavigationDrawerFragment : DialogFragment() {
{ pair ->
val gearEvent = pair.first.events.firstOrNull { it.gear }
createUpdatingJob("seasonal", {
- gearEvent?.end?.after(Date()) == true || pair.second.isNotEmpty()
+ gearEvent?.isCurrentlyActive == true || pair.second.isNotEmpty()
}, {
val diff = (gearEvent?.end?.time ?: 0) - Date().time
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
}) {
- updateSeasonalMenuEntries(pair.first, pair.second)
+ updateSeasonalMenuEntries(gearEvent, pair.second)
}
},
RxErrorHandler.handleEmptyError()
@@ -278,6 +269,7 @@ class NavigationDrawerFragment : DialogFragment() {
it.quest?.key ?: ""
}
.flatMapMaybe { inventoryRepository.getQuestContent(it).firstElement() }
+ .filter { (it.boss?.hp ?: 0) > 0 }
.subscribe(
{
questContent = it
@@ -310,7 +302,7 @@ class NavigationDrawerFragment : DialogFragment() {
}
}
- private fun updateSeasonalMenuEntries(worldState: WorldState, items: List- ) {
+ private fun updateSeasonalMenuEntries(gearEvent: WorldStateEvent?, items: List
- ) {
val market = getItemWithIdentifier(SIDEBAR_SHOPS_MARKET) ?: return
if (items.isNotEmpty() && items.firstOrNull()?.event?.end?.after(Date()) == true) {
market.pillText = context?.getString(R.string.something_new)
@@ -323,8 +315,7 @@ class NavigationDrawerFragment : DialogFragment() {
val shop = getItemWithIdentifier(SIDEBAR_SHOPS_SEASONAL) ?: return
shop.pillText = context?.getString(R.string.open)
- val gearEvent = worldState.events.firstOrNull { it.gear }
- if (gearEvent?.end?.after(Date()) == true) {
+ if (gearEvent?.isCurrentlyActive == true) {
shop.isVisible = true
shop.subtitle = context?.getString(R.string.open_for, gearEvent.end?.getShortRemainingString())
} else {
@@ -334,7 +325,7 @@ class NavigationDrawerFragment : DialogFragment() {
}
private fun updateUser(user: User) {
- setMessagesCount(user.inbox?.newMessages ?: 0)
+ setMessagesCount(user.inbox)
setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0)
setDisplayName(user.profile?.name)
setUsername(user.formattedUsername)
@@ -629,12 +620,23 @@ class NavigationDrawerFragment : DialogFragment() {
}
}
- private fun setMessagesCount(unreadMessages: Int) {
- if (unreadMessages == 0) {
- binding?.messagesBadge?.visibility = View.GONE
- } else {
+ private fun setMessagesCount(inbox: Inbox?) {
+ val numOfUnreadMessages = inbox?.newMessages ?: 0
+ if (numOfUnreadMessages != 0) {
binding?.messagesBadge?.visibility = View.VISIBLE
- binding?.messagesBadge?.text = unreadMessages.toString()
+ binding?.messagesBadge?.text = numOfUnreadMessages.toString()
+ context?.let {
+ val color = if (inbox?.hasUserSeenInbox != true) {
+ it.getThemeColor(R.attr.colorAccent)
+ } else {
+ ContextCompat.getColor(it, R.color.gray_200)
+ }
+ val background = binding?.messagesBadge?.background as? GradientDrawable
+ background?.color = ColorStateList.valueOf(color)
+ binding?.messagesBadge?.setTextColor(ContextCompat.getColor(it, R.color.white))
+ }
+ } else {
+ binding?.messagesBadge?.visibility = View.GONE
}
}
@@ -652,13 +654,9 @@ class NavigationDrawerFragment : DialogFragment() {
activePromo = configManager.activePromo()
val promoItem = getItemWithIdentifier(SIDEBAR_PROMO) ?: return
activePromo?.let { activePromo ->
- if (sharedPreferences.getBoolean("hide${activePromo.identifier}", false)) {
- promoItem.isVisible = true
- adapter.activePromo = activePromo
- } else {
- promoItem.isVisible = false
- }
-
+ promoItem.isVisible =
+ !sharedPreferences.getBoolean("hide${activePromo.identifier}", false)
+ adapter.activePromo = activePromo
var promotedItem: HabiticaDrawerItem? = null
if (activePromo.promoType == PromoType.GEMS_AMOUNT || activePromo.promoType == PromoType.GEMS_PRICE) {
promotedItem = getItemWithIdentifier(SIDEBAR_GEMS)
@@ -670,13 +668,18 @@ class NavigationDrawerFragment : DialogFragment() {
promotedItem.pillText = context?.getString(R.string.sale)
promotedItem.pillBackground = context?.let { activePromo.pillBackgroundDrawable(it) }
createUpdatingJob(activePromo.promoType.name, {
- activePromo.endDate.after(Date())
+ activePromo.isActive
}, {
val diff = activePromo.endDate.time - Date().time
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
}) {
- promotedItem.subtitle = context?.getString(R.string.x_remaining, activePromo.endDate.getShortRemainingString())
- updateItem(promotedItem)
+ if (activePromo.isActive) {
+ promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
+ updateItem(promotedItem)
+ } else {
+ promotedItem.subtitle = null
+ updateItem(promotedItem)
+ }
}
} ?: run {
promoItem.isVisible = false
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
index 1ee163b59..665316463 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
@@ -33,7 +33,6 @@ class EquipmentDetailFragment :
}
var type: String? = null
- var typeText: String? = null
var equippedGear: String? = null
var isCostume: Boolean? = null
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt
index 45101cbb1..2d0fb80de 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentOverviewFragment.kt
@@ -4,38 +4,26 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.fragment.app.viewModels
import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentEquipmentOverviewBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
-import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.Gear
-import com.habitrpg.android.habitica.models.user.User
+import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
-import javax.inject.Inject
+import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel
+import com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
class EquipmentOverviewFragment : BaseMainFragment() {
+ private val viewModel: EquipmentOverviewViewModel by viewModels()
+
override var binding: FragmentEquipmentOverviewBinding? = null
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentEquipmentOverviewBinding {
return FragmentEquipmentOverviewBinding.inflate(inflater, container, false)
}
- @Inject
- lateinit var inventoryRepository: InventoryRepository
-
- override var user: User?
- get() = super.user
- set(value) {
- super.user = value
- if (this::inventoryRepository.isInitialized) {
- value?.items?.gear?.let {
- updateGearData(it)
- }
- }
- }
-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -46,22 +34,24 @@ class EquipmentOverviewFragment : BaseMainFragment
+ if (isChecked == viewModel.user.value?.preferences?.autoEquip) return@setOnCheckedChangeListener
+ viewModel.updateUser("preferences.autoEquip", isChecked) }
+ binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked == viewModel.user.value?.preferences?.costume) return@setOnCheckedChangeListener
+ viewModel.updateUser("preferences.costume", isChecked) }
- binding?.autoEquipSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.autoEquip", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) }
- binding?.costumeSwitch?.setOnCheckedChangeListener { _, isChecked -> userRepository.updateUser("preferences.costume", isChecked).subscribe({ }, RxErrorHandler.handleEmptyError()) }
+ viewModel.user.observe(viewLifecycleOwner) {
+ it?.items?.gear?.let {
+ updateGearData(it)
+ }
+ binding?.autoEquipSwitch?.isChecked = user?.preferences?.autoEquip ?: false
+ binding?.costumeSwitch?.isChecked = user?.preferences?.costume ?: false
- user?.items?.gear?.let {
- updateGearData(it)
+ binding?.costumeView?.isEnabled = user?.preferences?.costume == true
}
}
- override fun onDestroy() {
- inventoryRepository.close()
- super.onDestroy()
- }
-
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
@@ -71,31 +61,17 @@ class EquipmentOverviewFragment : BaseMainFragment(), GemPurchaseActivity.CheckoutFragment {
@@ -98,8 +99,10 @@ class GemsPurchaseFragment : BaseFragment(), GemPurc
override fun setupCheckout() {
CoroutineScope(Dispatchers.IO).launch {
val skus = purchaseHandler?.getAllGemSKUs()
- for (sku in skus ?: emptyList()) {
- updateButtonLabel(sku.id.code, sku.price)
+ withContext(Dispatchers.Main) {
+ for (sku in skus ?: emptyList()) {
+ updateButtonLabel(sku.id.code, sku.price)
+ }
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt
index d8f400fb5..d77903a25 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt
@@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
class GiftPurchaseGemsFragment : BaseFragment() {
@@ -63,8 +64,10 @@ class GiftPurchaseGemsFragment : BaseFragment()
fun setupCheckout() {
CoroutineScope(Dispatchers.IO).launch {
val skus = purchaseHandler?.getAllGemSKUs()
- for (sku in skus ?: emptyList()) {
- updateButtonLabel(sku.id.code, sku.price)
+ withContext(Dispatchers.Main) {
+ for (sku in skus ?: emptyList()) {
+ updateButtonLabel(sku.id.code, sku.price)
+ }
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
index ba4a1381d..d35565f89 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
@@ -35,6 +35,7 @@ import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionVi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.Subscribe
import org.solovyev.android.checkout.Inventory
import org.solovyev.android.checkout.Purchase
@@ -145,12 +146,15 @@ class SubscriptionFragment : BaseFragment(), GemPur
override fun setupCheckout() {
CoroutineScope(Dispatchers.IO).launch {
val subscriptions = purchaseHandler?.getAllSubscriptionProducts() ?: return@launch
- for (sku in subscriptions.skus) {
- updateButtonLabel(sku, sku.price, subscriptions)
+ skus = subscriptions.skus
+ withContext(Dispatchers.Main) {
+ for (sku in subscriptions.skus) {
+ updateButtonLabel(sku, sku.price, subscriptions)
+ }
+ selectSubscription(PurchaseTypes.Subscription1Month)
+ hasLoadedSubscriptionOptions = true
+ updateSubscriptionInfo()
}
- selectSubscription(PurchaseTypes.Subscription1Month)
- hasLoadedSubscriptionOptions = true
- updateSubscriptionInfo()
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
index 610056e76..1086892e0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
@@ -101,7 +101,7 @@ class ChatFragment : BaseFragment() {
viewModel?.updateUser("flags.communityGuidelinesAccepted", true)
}
- viewModel?.getUserData()?.observe(
+ viewModel?.user?.observe(
viewLifecycleOwner,
{
chatAdapter?.user = it
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
index 6e189dfd1..5603a0ca5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
@@ -88,7 +88,12 @@ class InboxMessageListFragment : BaseMainFragment) {
+ socialRepository.markSomePrivateMessagesAsRead(user, messages)
+ }
+
private fun startAutoRefreshing() {
if (refreshDisposable != null && refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
index 5d2de4c30..093539c71 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
@@ -111,21 +111,22 @@ class GuildDetailFragment : BaseFragment() {
private val sendInvitesResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val inviteData = HashMap()
- inviteData["inviter"] = viewModel?.getUserData()?.value?.profile?.name ?: ""
- if (it.data?.getBooleanExtra(GroupInviteActivity.IS_EMAIL_KEY, false) == true) {
- val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
+ inviteData["inviter"] = viewModel?.user?.value?.profile?.name ?: ""
+ val emails = it.data?.getStringArrayExtra(GroupInviteActivity.EMAILS_KEY)
+ if (emails != null && emails.isNotEmpty()) {
val invites = ArrayList>()
- emails?.forEach { email ->
+ emails.forEach { email ->
val invite = HashMap()
invite["name"] = ""
invite["email"] = email
invites.add(invite)
}
inviteData["emails"] = invites
- } else {
- val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY)
- val invites = mutableListOf()
- userIDs?.forEach { invites.add(it) }
+ }
+ val userIDs = it.data?.getStringArrayExtra(GroupInviteActivity.USER_IDS_KEY)
+ if (userIDs != null && userIDs.isNotEmpty()) {
+ val invites = ArrayList()
+ userIDs.forEach { invites.add(it) }
inviteData["usernames"] = invites
}
viewModel?.inviteToGroup(inviteData)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
index 8dfdb6ec6..1ca9c25bf 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
@@ -114,7 +114,7 @@ class PartyDetailFragment : BaseFragment() {
}
viewModel?.getGroupData()?.observe(viewLifecycleOwner, { updateParty(it) })
- viewModel?.getUserData()?.observe(viewLifecycleOwner, { updateUser(it) })
+ viewModel?.user?.observe(viewLifecycleOwner, { updateUser(it) })
viewModel?.getMembersData()?.observe(viewLifecycleOwner, { updateMembersList(it) })
}
@@ -269,7 +269,7 @@ class PartyDetailFragment : BaseFragment() {
}
) ?: return@forEachIndexed
val viewHolder = GroupMemberViewHolder(memberView)
- viewHolder.bind(member, leaderID ?: "", viewModel?.getUserData()?.value?.id)
+ viewHolder.bind(member, leaderID ?: "", viewModel?.user?.value?.id)
viewHolder.onClickEvent = {
FullProfileActivity.open(member.id ?: "")
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt
index 32417f81d..23551b00d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt
@@ -7,6 +7,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
@@ -17,10 +18,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel()
@Inject
lateinit var userRepository: UserRepository
- private val user: MutableLiveData by lazy {
+ private val _user: MutableLiveData by lazy {
loadUserFromLocal()
MutableLiveData()
}
+ val user: LiveData by lazy {
+ _user
+ }
init {
if (initializeComponent) {
@@ -38,13 +42,13 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel()
internal val disposable = CompositeDisposable()
- fun getUserData(): LiveData = user
-
private fun loadUserFromLocal() {
- disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()).subscribe({ user.value = it }, RxErrorHandler.handleEmptyError()))
+ disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError()))
}
fun updateUser(path: String, value: Any) {
- disposable.add(userRepository.updateUser(path, value).subscribe({ }, RxErrorHandler.handleEmptyError()))
+ disposable.add(userRepository.updateUser(path, value)
+ .subscribe({ }, RxErrorHandler.handleEmptyError()))
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
index 37cbd2372..1696cd616 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
@@ -61,7 +61,7 @@ open class NotificationsViewModel : BaseViewModel() {
var notifications = convertInvitationsToNotifications(it)
if (it.flags?.newStuff == true) {
val notification = Notification()
- notification.id = "new-stuff-notification"
+ notification.id = "custom-new-stuff-notification"
notification.type = Notification.Type.NEW_STUFF.type
val data = NewStuffData()
notification.data = data
@@ -179,11 +179,19 @@ open class NotificationsViewModel : BaseViewModel() {
* instead of one of the ones coming from server.
*/
private fun isCustomNotification(notification: Notification): Boolean {
- return notification.id.startsWith("custom-") || notification.id == "new-stuff-notification"
+ return notification.id.startsWith("custom-")
}
+ private fun isCustomNewStuffNotification(notification: Notification) =
+ notification.id == "custom-new-stuff-notification"
+
fun dismissNotification(notification: Notification) {
if (isCustomNotification(notification)) {
+ if (isCustomNewStuffNotification(notification)) {
+ customNotifications.onNext(
+ customNotifications.value?.filterNot { it.id == notification.id }
+ )
+ }
return
}
@@ -199,6 +207,13 @@ open class NotificationsViewModel : BaseViewModel() {
.filter { !actionableNotificationTypes.contains(it.type) }
.map { it.id }
+ val customNewStuffNotification = notifications
+ .firstOrNull { isCustomNewStuffNotification(it) }
+
+ if (customNewStuffNotification != null) {
+ dismissNotification(customNewStuffNotification)
+ }
+
if (dismissableIds.isEmpty()) {
return
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt
index effce0176..03d59dc03 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt
@@ -20,7 +20,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
internal val isUserOnQuest: Boolean
get() = !(
- getGroupData().value?.quest?.members?.none { it.key == getUserData().value?.id }
+ getGroupData().value?.quest?.members?.none { it.key == user.value?.id }
?: true
)
@@ -88,7 +88,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
}
fun showParticipantButtons(): Boolean {
- val user = getUserData().value
+ val user = user.value
return !(user?.party == null || user.party?.quest == null) && !isQuestActive && user.party?.quest?.RSVPNeeded == true
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt
new file mode 100644
index 000000000..ebc740ec1
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModel.kt
@@ -0,0 +1,25 @@
+package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment
+
+import androidx.lifecycle.SavedStateHandle
+import com.habitrpg.android.habitica.components.UserComponent
+import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.models.inventory.Equipment
+import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
+import javax.inject.Inject
+
+class EquipmentOverviewViewModel(savedStateHandle: SavedStateHandle): BaseViewModel() {
+
+ @Inject
+ lateinit var inventoryRepository: InventoryRepository
+
+ override fun inject(component: UserComponent) {
+ component.inject(this)
+ }
+
+ fun getGear(key: String, onSuccess: (Equipment) -> Unit) {
+ disposable.add(inventoryRepository.getEquipment(key).subscribe( {
+ onSuccess(it)
+ }, RxErrorHandler.handleEmptyError()))
+ }
+}
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 4ae3dba98..542349857 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
@@ -312,12 +312,12 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style.
private fun checkIfQueueAvailable(): Boolean {
val currentDialog = dialogQueue.firstOrNull() ?: return true
- if (currentDialog.isShowing) {
- return false
+ return if (currentDialog.isShowing && currentDialog.getActivity()?.isFinishing != true) {
+ false
} else {
// The Dialog was probably dismissed in a weird way. Clear it out so that the queue doesn't get stuck
dialogQueue.removeAt(0)
- return true
+ true
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt
index 4e0f0bfe9..3e80cb578 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt
@@ -36,6 +36,7 @@ class QuestMenuView : LinearLayout {
orientation = VERTICAL
binding.heartIconView.setImageBitmap(HabiticaIconsHelper.imageOfHeartDarkBg())
+ binding.rageIconView.setImageBitmap(HabiticaIconsHelper.imageOfRage())
binding.pendingDamageIconView.setImageBitmap(HabiticaIconsHelper.imageOfDamage())
@@ -50,16 +51,21 @@ class QuestMenuView : LinearLayout {
fun configure(quest: Quest) {
binding.healthBarView.setCurrentValue(quest.progress?.hp ?: 0.0)
+ binding.rageBarView.setCurrentValue(quest.progress?.rage ?: 0.0)
}
fun configure(questContent: QuestContent) {
this.questContent = questContent
binding.healthBarView.setMaxValue(questContent.boss?.hp?.toDouble() ?: 0.0)
- binding.bottomView.setBackgroundColor(questContent.colors?.darkColor ?: 0)
- //binding.bossArtView.setBackgroundColor(questContent.colors?.mediumColor ?: 0)
- //DataBindingUtils.loadImage(binding.bossArtView, "quest_" + questContent.key)
binding.bossNameView.text = questContent.boss?.name
binding.typeTextView.text = context.getString(R.string.boss_quest)
+
+ if (questContent.boss?.hasRage == true) {
+ binding.rageView.visibility = View.VISIBLE
+ binding.rageBarView.setMaxValue(questContent.boss?.rage?.value ?: 0.0)
+ } else {
+ binding.rageView.visibility = View.GONE
+ }
}
fun configure(user: User) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
index 5cb70d0a9..bc02379a1 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
@@ -224,6 +224,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
if (editedTags.containsKey(tag.id)) {
editedTags.remove(tag.id)
}
+ activeTags.remove(tag.id)
tags.remove(tag)
tagsList.removeView(wrapper)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt
index 2b46746e8..f2af43075 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/ReminderContainer.kt
@@ -55,6 +55,12 @@ class ReminderContainer @JvmOverloads constructor(
}
var firstDayOfWeek: Int? = null
+ set(value) {
+ children
+ .filterIsInstance()
+ .forEach { it.firstDayOfWeek = value }
+ field = value
+ }
init {
orientation = VERTICAL
diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt
index a0fb0b394..d06852bc4 100644
--- a/fastlane/changelog.txt
+++ b/fastlane/changelog.txt
@@ -1 +1 @@
-We’ve improved more of our notifications so they bring you to the relevant screen when tapped. When customizing your avatar, you’ll be able to see your avatar updating in real time as you select different options. You can also see your current Gems when gifting Gems from your balance now. We’ve fixed a few bugs too, including one where tasks wouldn’t load when the app is left running in the background, the stats widget works again, and another where Dailies wouldn’t filter properly on launch.
+In this update we’ve fixed some bugs, added more seasonal event support, and made a few quality of life improvements! Sending Guild and Party invites through email should work more reliably. We’ve improved task drag and drop logic when filters are applied to make the order more consistent. Habit streak is now referred to as ‘Habit Counter’ to better reflect the actual behavior. Issues with subscriptions not cancelling fully or getting errors when buying multiple Gem packages should be resolved.