From cedc06f1e938ea592f02d8f06159e898aede3d60 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 15 Nov 2022 16:06:43 +0100 Subject: [PATCH] port more code to coroutines --- .../android/habitica/api/ApiService.kt | 33 ++-- .../android/habitica/data/ApiClient.kt | 29 ++-- .../habitica/data/InventoryRepository.kt | 16 +- .../android/habitica/data/UserRepository.kt | 27 ++-- .../data/implementation/ApiClientImpl.kt | 90 +++++------ .../implementation/InventoryRepositoryImpl.kt | 83 +++++----- .../data/implementation/UserRepositoryImpl.kt | 147 +++++++++--------- .../data/local/UserLocalRepository.kt | 3 +- .../RealmUserLocalRepository.kt | 52 ++++--- .../habitica/helpers/ExceptionHandler.kt | 6 + .../android/habitica/models/tasks/Task.kt | 4 + .../habitica/ui/activities/ArmoireActivity.kt | 30 ++-- .../habitica/ui/activities/DeathActivity.kt | 9 +- .../habitica/ui/activities/SetupActivity.kt | 22 +-- .../ui/activities/VerifyUsernameActivity.kt | 11 +- .../CustomizationRecyclerViewAdapter.kt | 15 +- .../setup/CustomizationSetupAdapter.kt | 5 +- .../habitica/ui/fragments/NewsFragment.kt | 8 +- .../habitica/ui/fragments/StatsFragment.kt | 17 +- .../AvatarCustomizationFragment.kt | 17 +- .../customization/AvatarOverviewFragment.kt | 8 +- .../inventory/items/ItemRecyclerFragment.kt | 17 +- .../preferences/AccountPreferenceFragment.kt | 18 ++- .../EmailNotificationsPreferencesFragment.kt | 10 +- .../preferences/PreferencesFragment.kt | 42 +++-- .../PushNotificationsPreferencesFragment.kt | 7 +- .../ui/fragments/setup/AvatarSetupFragment.kt | 14 +- .../ui/fragments/skills/SkillsFragment.kt | 20 +-- .../fragments/support/SupportMainFragment.kt | 10 +- .../tasks/RewardsRecyclerviewFragment.kt | 40 +++-- .../habitica/ui/viewmodels/BaseViewModel.kt | 7 +- .../ui/viewmodels/MainUserViewModel.kt | 12 +- .../ui/views/dialogs/PetSuggestHatchDialog.kt | 26 ++-- .../habitica/ui/views/shops/PurchaseDialog.kt | 90 +++++------ shared/build.gradle.kts | 2 +- .../shared/habitica/PlatformLogger.kt | 4 + .../com/habitrpg/shared/habitica/Logger.kt | 4 +- .../shared/habitica/PlatformLogger.kt | 12 +- 38 files changed, 495 insertions(+), 472 deletions(-) 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 ab3fe3f3c..8fb909988 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 @@ -72,41 +72,38 @@ interface ApiService { suspend fun getContent(@Query("language") language: String?): HabitResponse @PUT("user/") - fun updateUser(@Body updateDictionary: Map): Flowable> + suspend fun updateUser(@Body updateDictionary: Map): HabitResponse @PUT("user/") fun registrationLanguage(@Header("Accept-Language") registrationLanguage: String): Flowable> @GET("user/in-app-rewards") - fun retrieveInAppRewards(): Flowable>> - - @GET("user/inventory/buy") - fun retrieveOldGearRewards(): Flowable>> + suspend fun retrieveInAppRewards(): HabitResponse> @POST("user/equip/{type}/{key}") fun equipItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> @POST("user/buy/{key}") - fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): Flowable> + suspend fun buyItem(@Path("key") itemKey: String, @Body quantity: Map): HabitResponse @POST("user/purchase/{type}/{key}") - fun purchaseItem( + suspend fun purchaseItem( @Path("type") type: String, @Path("key") itemKey: String, @Body quantity: Map - ): Flowable> + ): HabitResponse @POST("user/purchase-hourglass/{type}/{key}") - fun purchaseHourglassItem(@Path("type") type: String, @Path("key") itemKey: String): Flowable> + suspend fun purchaseHourglassItem(@Path("type") type: String, @Path("key") itemKey: String): HabitResponse @POST("user/buy-mystery-set/{key}") - fun purchaseMysterySet(@Path("key") itemKey: String): Flowable> + suspend fun purchaseMysterySet(@Path("key") itemKey: String): HabitResponse @POST("user/buy-quest/{key}") - fun purchaseQuest(@Path("key") key: String): Flowable> + suspend fun purchaseQuest(@Path("key") key: String): HabitResponse @POST("user/buy-special-spell/{key}") - fun purchaseSpecialSpell(@Path("key") key: String): Flowable> + suspend fun purchaseSpecialSpell(@Path("key") key: String): HabitResponse @POST("user/sell/{type}/{key}") fun sellItem(@Path("type") itemType: String, @Path("key") itemKey: String): Flowable> @@ -124,7 +121,7 @@ interface ApiService { fun getTasks(@Query("type") type: String, @Query("dueDate") dueDate: String): Flowable> @POST("user/unlock") - fun unlockPath(@Query("path") path: String): Flowable> + suspend fun unlockPath(@Query("path") path: String): HabitResponse @GET("tasks/{id}") fun getTask(@Path("id") id: String): Flowable> @@ -183,14 +180,14 @@ interface ApiService { suspend fun revive(): HabitResponse @POST("user/class/cast/{skill}") - fun useSkill( + suspend fun useSkill( @Path("skill") skillName: String, @Query("targetType") targetType: String, @Query("targetId") targetId: String - ): Flowable> + ): HabitResponse @POST("user/class/cast/{skill}") - fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): Flowable> + suspend fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): HabitResponse @POST("user/change-class") suspend fun changeClass(): HabitResponse @@ -407,13 +404,13 @@ interface ApiService { fun deleteAccount(@Body body: Map): Flowable> @GET("user/toggle-pinned-item/{pinType}/{path}") - fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): Flowable> + suspend fun togglePinnedItem(@Path("pinType") pinType: String, @Path("path") path: String): HabitResponse @POST("user/reset-password") fun sendPasswordResetEmail(@Body data: Map): Flowable> @PUT("user/auth/update-username") - fun updateLoginName(@Body data: Map): Flowable> + suspend fun updateLoginName(@Body data: Map): HabitResponse @POST("user/auth/verify-username") fun verifyUsername(@Body data: Map): Flowable> 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 26dbacaac..907486b5e 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 @@ -58,25 +58,24 @@ interface ApiClient { fun setLanguageCode(languageCode: String) suspend fun getContent(language: String? = null): ContentResult? - fun updateUser(updateDictionary: Map): Flowable + suspend fun updateUser(updateDictionary: Map): User? fun registrationLanguage(registrationLanguage: String): Flowable - fun retrieveInAppRewards(): Flowable> - fun retrieveOldGear(): Flowable> + suspend fun retrieveInAppRewards(): List? fun equipItem(type: String, itemKey: String): Flowable - fun buyItem(itemKey: String, purchaseQuantity: Int): Flowable + suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? - fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Flowable + suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? - fun purchaseHourglassItem(type: String, itemKey: String): Flowable + suspend fun purchaseHourglassItem(type: String, itemKey: String): Void? - fun purchaseMysterySet(itemKey: String): Flowable + suspend fun purchaseMysterySet(itemKey: String): Void? - fun purchaseQuest(key: String): Flowable - fun purchaseSpecialSpell(key: String): Flowable + suspend fun purchaseQuest(key: String): Void? + suspend fun purchaseSpecialSpell(key: String): Void? fun validateSubscription(request: PurchaseValidationRequest): Flowable fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable suspend fun cancelSubscription(): Void? @@ -89,7 +88,7 @@ interface ApiClient { fun getTasks(type: String): Flowable fun getTasks(type: String, dueDate: String): Flowable - fun unlockPath(path: String): Flowable + suspend fun unlockPath(path: String): UnlockResponse? fun getTask(id: String): Flowable @@ -126,9 +125,9 @@ interface ApiClient { suspend fun sleep(): Boolean? suspend fun revive(): User? - fun useSkill(skillName: String, targetType: String, targetId: String): Flowable + suspend fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? - fun useSkill(skillName: String, targetType: String): Flowable + suspend fun useSkill(skillName: String, targetType: String): SkillResponse? suspend fun changeClass(className: String?): User? @@ -251,12 +250,12 @@ interface ApiClient { fun resetAccount(): Flowable fun deleteAccount(password: String): Flowable - fun togglePinnedItem(pinType: String, path: String): Flowable + suspend fun togglePinnedItem(pinType: String, path: String): Void? fun sendPasswordResetEmail(email: String): Flowable - fun updateLoginName(newLoginName: String, password: String): Flowable - fun updateUsername(newLoginName: String): Flowable + suspend fun updateLoginName(newLoginName: String, password: String): Void? + suspend fun updateUsername(newLoginName: String): Void? fun updateEmail(newEmail: String, password: String): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt index 8fa5eea2c..7cacbe430 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt @@ -39,7 +39,7 @@ interface InventoryRepository : BaseRepository { fun getQuestContent(keys: List): Flow> fun getEquipment(searchedKeys: List): Flowable> - fun retrieveInAppRewards(): Flowable> + suspend fun retrieveInAppRewards(): List? fun getOwnedEquipment(type: String): Flowable> fun getEquipmentType(type: String, set: String): Flowable> @@ -71,21 +71,21 @@ interface InventoryRepository : BaseRepository { fun inviteToQuest(quest: QuestContent): Flowable - fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): Flowable + suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? fun retrieveShopInventory(identifier: String): Flowable fun retrieveMarketGear(): Flowable - fun purchaseMysterySet(categoryIdentifier: String): Flowable + suspend fun purchaseMysterySet(categoryIdentifier: String): Void? - fun purchaseHourglassItem(purchaseType: String, key: String): Flowable + suspend fun purchaseHourglassItem(purchaseType: String, key: String): Void? - fun purchaseQuest(key: String): Flowable - fun purchaseSpecialSpell(key: String): Flowable + suspend fun purchaseQuest(key: String): Void? + suspend fun purchaseSpecialSpell(key: String): Void? - fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable + suspend fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Void? - fun togglePinnedItem(item: ShopItem): Flowable> + suspend fun togglePinnedItem(item: ShopItem): List? fun getItems(itemClass: Class, keys: Array): Flow> fun getItemsFlowable(itemClass: Class): Flowable> fun getItems(itemClass: Class): Flow> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt index 2c957f717..220c675a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt @@ -1,6 +1,5 @@ package com.habitrpg.android.habitica.data -import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement import com.habitrpg.android.habitica.models.Skill @@ -8,14 +7,14 @@ import com.habitrpg.android.habitica.models.TeamPlan import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.responses.UnlockResponse -import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse import com.habitrpg.android.habitica.models.social.Group -import com.habitrpg.shared.habitica.models.tasks.Attribute import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.models.user.UserQuestStatus +import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse +import com.habitrpg.shared.habitica.models.tasks.Attribute import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Maybe import kotlinx.coroutines.flow.Flow interface UserRepository : BaseRepository { @@ -23,14 +22,14 @@ interface UserRepository : BaseRepository { fun getUserFlowable(): Flowable fun getUser(userID: String): Flow - fun updateUser(updateData: Map): Flowable - fun updateUser(key: String, value: Any): Flowable + suspend fun updateUser(updateData: Map): User? + suspend fun updateUser(key: String, value: Any): User? suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User? suspend fun revive(): User? - fun resetTutorial(): Maybe + suspend fun resetTutorial(): User? suspend fun sleep(user: User): User? @@ -38,14 +37,14 @@ interface UserRepository : BaseRepository { fun getSpecialItems(user: User): Flowable> - fun useSkill(key: String, target: String?, taskId: String): Flowable - fun useSkill(key: String, target: String?): Flowable + suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? + suspend fun useSkill(key: String, target: String?): SkillResponse? suspend fun disableClasses(): User? suspend fun changeClass(selectedClass: String? = null): User? - fun unlockPath(path: String, price: Int): Flowable - fun unlockPath(customization: Customization): Flowable + suspend fun unlockPath(path: String, price: Int): UnlockResponse? + suspend fun unlockPath(customization: Customization): UnlockResponse? suspend fun runCron(tasks: MutableList) suspend fun runCron() @@ -56,14 +55,14 @@ interface UserRepository : BaseRepository { fun changeCustomDayStart(dayStartTime: Int): Flowable - fun updateLanguage(languageCode: String): Flowable + suspend fun updateLanguage(languageCode: String): User? suspend fun resetAccount(): User? fun deleteAccount(password: String): Flowable fun sendPasswordResetEmail(email: String): Flowable - fun updateLoginName(newLoginName: String, password: String? = null): Maybe + suspend fun updateLoginName(newLoginName: String, password: String? = null): User? fun updateEmail(newEmail: String, password: String): Flowable fun updatePassword(oldPassword: String, newPassword: String, newPasswordConfirmation: String): Flowable fun verifyUsername(username: String): Flowable @@ -71,7 +70,7 @@ interface UserRepository : BaseRepository { fun allocatePoint(stat: Attribute): Flowable fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable - fun useCustomization(type: String, category: String?, identifier: String): Flowable + suspend fun useCustomization(type: String, category: String?, identifier: String): User? fun retrieveAchievements(): Flowable> fun getAchievements(): Flow> fun getQuestAchievements(): Flow> 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 ceecbb0c4..c42556b06 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 @@ -105,7 +105,7 @@ class ApiClientImpl( return habitResponse.data } - suspend fun handleSuspendCall(apiCall: suspend () -> HabitResponse): T? { + private suspend fun process(apiCall: suspend () -> HabitResponse): T? { try { return processResponse(apiCall()) } catch (throwable: Throwable) { @@ -280,14 +280,14 @@ class ApiClientImpl( } override suspend fun retrieveUser(withTasks: Boolean): User? { - val user = handleSuspendCall { apiService.getUser() } + val user = process { apiService.getUser() } val tasks = getTasks() user?.tasks = tasks return user } override suspend fun retrieveInboxMessages(uuid: String, page: Int): List? { - return handleSuspendCall { apiService.getInboxMessages(uuid, page) } + return process { apiService.getInboxMessages(uuid, page) } } override fun retrieveInboxConversations(): Flowable> { @@ -346,34 +346,30 @@ class ApiClientImpl( this.languageCode = languageCode } - override suspend fun getStatus(): Status? = handleSuspendCall { apiService.getStatus() } + override suspend fun getStatus(): Status? = process { apiService.getStatus() } override suspend fun getContent(language: String?): ContentResult? { - return handleSuspendCall { apiService.getContent(language) } + return process { apiService.getContent(language) } } - override fun updateUser(updateDictionary: Map): Flowable { - return apiService.updateUser(updateDictionary).compose(configureApiCallObserver()) + override suspend fun updateUser(updateDictionary: Map): User? { + return process { apiService.updateUser(updateDictionary) } } override fun registrationLanguage(registrationLanguage: String): Flowable { return apiService.registrationLanguage(registrationLanguage).compose(configureApiCallObserver()) } - override fun retrieveInAppRewards(): Flowable> { - return apiService.retrieveInAppRewards().compose(configureApiCallObserver()) - } - - override fun retrieveOldGear(): Flowable> { - return apiService.retrieveOldGearRewards().compose(configureApiCallObserver()) + override suspend fun retrieveInAppRewards(): List? { + return process { apiService.retrieveInAppRewards() } } override fun equipItem(type: String, itemKey: String): Flowable { return apiService.equipItem(type, itemKey).compose(configureApiCallObserver()) } - override fun buyItem(itemKey: String, purchaseQuantity: Int): Flowable { - return apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))).compose(configureApiCallObserver()) + override suspend fun buyItem(itemKey: String, purchaseQuantity: Int): BuyResponse? { + return process { apiService.buyItem(itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } override fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable { @@ -384,8 +380,8 @@ class ApiClientImpl( return apiService.blockMember(userID).compose(configureApiCallObserver()) } - override fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Flowable { - return apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))).compose(configureApiCallObserver()) + override suspend fun purchaseItem(type: String, itemKey: String, purchaseQuantity: Int): Void? { + return process { apiService.purchaseItem(type, itemKey, mapOf(Pair("quantity", purchaseQuantity))) } } override fun validateSubscription(request: PurchaseValidationRequest): Flowable { @@ -400,20 +396,20 @@ class ApiClientImpl( return processResponse(apiService.cancelSubscription()) } - override fun purchaseHourglassItem(type: String, itemKey: String): Flowable { - return apiService.purchaseHourglassItem(type, itemKey).compose(configureApiCallObserver()) + override suspend fun purchaseHourglassItem(type: String, itemKey: String): Void? { + return process { apiService.purchaseHourglassItem(type, itemKey) } } - override fun purchaseMysterySet(itemKey: String): Flowable { - return apiService.purchaseMysterySet(itemKey).compose(configureApiCallObserver()) + override suspend fun purchaseMysterySet(itemKey: String): Void? { + return process { apiService.purchaseMysterySet(itemKey) } } - override fun purchaseQuest(key: String): Flowable { - return apiService.purchaseQuest(key).compose(configureApiCallObserver()) + override suspend fun purchaseQuest(key: String): Void? { + return process { apiService.purchaseQuest(key) } } - override fun purchaseSpecialSpell(key: String): Flowable { - return apiService.purchaseSpecialSpell(key).compose(configureApiCallObserver()) + override suspend fun purchaseSpecialSpell(key: String): Void? { + return process { apiService.purchaseSpecialSpell(key) } } override fun sellItem(itemType: String, itemKey: String): Flowable { @@ -433,7 +429,7 @@ class ApiClientImpl( return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver()) } - override suspend fun getTasks(): TaskList? = handleSuspendCall { apiService.getTasks() } + override suspend fun getTasks(): TaskList? = process { apiService.getTasks() } override fun getTasks(type: String): Flowable { return apiService.getTasks(type).compose(configureApiCallObserver()) @@ -443,8 +439,8 @@ class ApiClientImpl( return apiService.getTasks(type, dueDate).compose(configureApiCallObserver()) } - override fun unlockPath(path: String): Flowable { - return apiService.unlockPath(path).compose(configureApiCallObserver()) + override suspend fun unlockPath(path: String): UnlockResponse? { + return process { apiService.unlockPath(path) } } override fun getTask(id: String): Flowable { @@ -452,7 +448,7 @@ class ApiClientImpl( } override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? { - return handleSuspendCall { apiService.postTaskDirection(id, direction) } + return process { apiService.postTaskDirection(id, direction) } } override fun bulkScoreTasks(data: List>): Flowable { @@ -464,7 +460,7 @@ class ApiClientImpl( } override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? { - return handleSuspendCall { apiService.scoreChecklistItem(taskId, itemId) } + return process { apiService.scoreChecklistItem(taskId, itemId) } } override fun createTask(item: Task): Flowable { @@ -495,20 +491,20 @@ class ApiClientImpl( return apiService.deleteTag(id).compose(configureApiCallObserver()) } - override suspend fun sleep(): Boolean? = handleSuspendCall { apiService.sleep() } + override suspend fun sleep(): Boolean? = process { apiService.sleep() } - override suspend fun revive(): User? = handleSuspendCall { apiService.revive() } + override suspend fun revive(): User? = process { apiService.revive() } - override fun useSkill(skillName: String, targetType: String, targetId: String): Flowable { - return apiService.useSkill(skillName, targetType, targetId).compose(configureApiCallObserver()) + suspend override fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? { + return process { apiService.useSkill(skillName, targetType, targetId) } } - override fun useSkill(skillName: String, targetType: String): Flowable { - return apiService.useSkill(skillName, targetType).compose(configureApiCallObserver()) + suspend override fun useSkill(skillName: String, targetType: String): SkillResponse? { + return process { apiService.useSkill(skillName, targetType) } } override suspend fun changeClass(className: String?): User? { - return handleSuspendCall { + return process { if (className != null) { apiService.changeClass(className) } else { @@ -517,7 +513,7 @@ class ApiClientImpl( } } - override suspend fun disableClasses(): User? = handleSuspendCall { apiService.disableClasses() } + override suspend fun disableClasses(): User? = process { apiService.disableClasses() } override fun markPrivateMessagesRead(): Flowable { // This is necessary, because the API call returns weird data. @@ -650,7 +646,7 @@ class ApiClientImpl( } override suspend fun postPrivateMessage(messageDetails: Map): PostChatMessageResult? { - return handleSuspendCall { apiService.postPrivateMessage(messageDetails) } + return process { apiService.postPrivateMessage(messageDetails) } } override fun retrieveShopIventory(identifier: String): Flowable { @@ -733,7 +729,7 @@ class ApiClientImpl( return apiService.runCron().compose(configureApiCallObserver()) } - override suspend fun reroll(): User? = handleSuspendCall { apiService.reroll() } + override suspend fun reroll(): User? = process { apiService.reroll() } override fun resetAccount(): Flowable { return apiService.resetAccount().compose(configureApiCallObserver()) @@ -745,8 +741,8 @@ class ApiClientImpl( return apiService.deleteAccount(updateObject).compose(configureApiCallObserver()) } - override fun togglePinnedItem(pinType: String, path: String): Flowable { - return apiService.togglePinnedItem(pinType, path).compose(configureApiCallObserver()) + override suspend fun togglePinnedItem(pinType: String, path: String): Void? { + return process { apiService.togglePinnedItem(pinType, path) } } override fun sendPasswordResetEmail(email: String): Flowable { @@ -755,17 +751,17 @@ class ApiClientImpl( return apiService.sendPasswordResetEmail(data).compose(configureApiCallObserver()) } - override fun updateLoginName(newLoginName: String, password: String): Flowable { + override suspend fun updateLoginName(newLoginName: String, password: String): Void? { val updateObject = HashMap() updateObject["username"] = newLoginName updateObject["password"] = password - return apiService.updateLoginName(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateLoginName(updateObject) } } - override fun updateUsername(newLoginName: String): Flowable { + override suspend fun updateUsername(newLoginName: String): Void? { val updateObject = HashMap() updateObject["username"] = newLoginName - return apiService.updateLoginName(updateObject).compose(configureApiCallObserver()) + return process { apiService.updateLoginName(updateObject) } } override fun verifyUsername(username: String): Flowable { @@ -831,7 +827,7 @@ class ApiClientImpl( return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver()) } - override suspend fun getWorldState(): WorldState? = handleSuspendCall { apiService.worldState() } + override suspend fun getWorldState(): WorldState? = process { apiService.worldState() } companion object { fun createGsonFactory(): GsonConverterFactory { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt index 3f11e468a..e8a165d56 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt @@ -47,8 +47,12 @@ class InventoryRepositoryImpl( return localRepository.getInAppRewards() } - override fun retrieveInAppRewards(): Flowable> { - return apiClient.retrieveInAppRewards().doOnNext { localRepository.saveInAppRewards(it) } + override suspend fun retrieveInAppRewards(): List? { + val rewards = apiClient.retrieveInAppRewards() + if (rewards != null) { + localRepository.saveInAppRewards(rewards) + } + return rewards } override fun getOwnedEquipment(type: String): Flowable> { @@ -238,35 +242,32 @@ class InventoryRepositoryImpl( .doOnNext { localRepository.changeOwnedCount("quests", quest.key, userID, -1) } } - override fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): Flowable { - return apiClient.buyItem(id, purchaseQuantity) - .doOnNext { buyResponse -> - if (user == null) { - return@doOnNext - } - val copiedUser = localRepository.getUnmanagedCopy(user) - if (buyResponse.items != null) { - copiedUser.items = buyResponse.items - } - if (buyResponse.hp != null) { - copiedUser.stats?.hp = buyResponse.hp - } - if (buyResponse.exp != null) { - copiedUser.stats?.exp = buyResponse.exp - } - if (buyResponse.mp != null) { - copiedUser.stats?.mp = buyResponse.mp - } - if (buyResponse.gp != null) { - copiedUser.stats?.gp = buyResponse.gp - } else { - copiedUser.stats?.gp = copiedUser.stats?.gp ?: 0 - (value * purchaseQuantity) - } - if (buyResponse.lvl != null) { - copiedUser.stats?.lvl = buyResponse.lvl - } - localRepository.save(copiedUser) - } + override suspend fun buyItem(user: User?, id: String, value: Double, purchaseQuantity: Int): BuyResponse? { + val buyResponse = apiClient.buyItem(id, purchaseQuantity) ?: return null + val foundUser = user ?: localRepository.getLiveUser(userID) ?: return buyResponse + val copiedUser = localRepository.getUnmanagedCopy(foundUser) + if (buyResponse.items != null) { + copiedUser.items = buyResponse.items + } + if (buyResponse.hp != null) { + copiedUser.stats?.hp = buyResponse.hp + } + if (buyResponse.exp != null) { + copiedUser.stats?.exp = buyResponse.exp + } + if (buyResponse.mp != null) { + copiedUser.stats?.mp = buyResponse.mp + } + if (buyResponse.gp != null) { + copiedUser.stats?.gp = buyResponse.gp + } else { + copiedUser.stats?.gp = (copiedUser.stats?.gp ?: 0.0) - (value * purchaseQuantity) + } + if (buyResponse.lvl != null) { + copiedUser.stats?.lvl = buyResponse.lvl + } + localRepository.save(copiedUser) + return buyResponse } override fun getAvailableLimitedItems(): Flowable> { @@ -281,30 +282,30 @@ class InventoryRepositoryImpl( return apiClient.retrieveMarketGear() } - override fun purchaseMysterySet(categoryIdentifier: String): Flowable { + override suspend fun purchaseMysterySet(categoryIdentifier: String): Void? { return apiClient.purchaseMysterySet(categoryIdentifier) } - override fun purchaseHourglassItem(purchaseType: String, key: String): Flowable { + override suspend fun purchaseHourglassItem(purchaseType: String, key: String): Void? { return apiClient.purchaseHourglassItem(purchaseType, key) } - override fun purchaseQuest(key: String): Flowable { + override suspend fun purchaseQuest(key: String): Void? { return apiClient.purchaseQuest(key) } - override fun purchaseSpecialSpell(key: String): Flowable { + override suspend fun purchaseSpecialSpell(key: String): Void? { return apiClient.purchaseSpecialSpell(key) } - override fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable { + override suspend fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Void? { return apiClient.purchaseItem(purchaseType, key, purchaseQuantity) } - override fun togglePinnedItem(item: ShopItem): Flowable> { - return if (!item.isValid) { - Flowable.empty() - } else apiClient.togglePinnedItem(item.pinType ?: "", item.path ?: "") - .flatMap { retrieveInAppRewards() } + override suspend fun togglePinnedItem(item: ShopItem): List? { + if (item.isValid) { + apiClient.togglePinnedItem(item.pinType ?: "", item.path ?: "") + } + return retrieveInAppRewards() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 5dd94d2d4..44a081976 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -30,6 +30,7 @@ import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.functions.BiFunction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import java.util.Date import java.util.GregorianCalendar @@ -51,24 +52,23 @@ class UserRepositoryImpl( override fun getUser(userID: String): Flow = localRepository.getUser(userID) - private fun updateUser(userID: String, updateData: Map): Flowable { - return Flowable.zip( - apiClient.updateUser(updateData), - localRepository.getUserFlowable(userID).firstElement().toFlowable() - ) { newUser, user -> mergeUser(user, newUser) } + private suspend fun updateUser(userID: String, updateData: Map): User? { + val networkUser = apiClient.updateUser(updateData) ?: return null + val oldUser = localRepository.getUser(userID).firstOrNull() + return mergeUser(oldUser, networkUser) } - private fun updateUser(userID: String, key: String, value: Any): Flowable { + private suspend fun updateUser(userID: String, key: String, value: Any): User? { val updateData = HashMap() updateData[key] = value return updateUser(userID, updateData) } - override fun updateUser(updateData: Map): Flowable { + override suspend fun updateUser(updateData: Map): User? { return updateUser(userID, updateData) } - override fun updateUser(key: String, value: Any): Flowable { + override suspend fun updateUser(key: String, value: Any): User? { return updateUser(userID, key, value) } @@ -90,12 +90,11 @@ class UserRepositoryImpl( val calendar = GregorianCalendar() val timeZone = calendar.timeZone val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS) - /*if (offset.toInt() != user.preferences?.timezoneOffset ?: 0) { - return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString()) + return if (offset.toInt() != (user.preferences?.timezoneOffset ?: 0)) { + updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString()) } else { - return@flatMap Flowable.just(user) - }*/ - return user + user + } } else { return null } @@ -106,17 +105,13 @@ class UserRepositoryImpl( return retrieveUser(false, true) } - override fun resetTutorial(): Maybe { - return localRepository.getTutorialSteps() - .firstElement() - .map> { tutorialSteps -> - val updateData = HashMap() - for (step in tutorialSteps) { - updateData[step.flagPath] = false - } - updateData - } - .flatMap { updateData -> updateUser(updateData).firstElement() } + override suspend fun resetTutorial(): User? { + val tutorialSteps = localRepository.getTutorialSteps().firstOrNull() ?: return null + val updateData = HashMap() + for (step in tutorialSteps) { + updateData[step.flagPath] = false + } + return updateUser(updateData) } override suspend fun sleep(user: User): User { @@ -134,26 +129,26 @@ class UserRepositoryImpl( override fun getSpecialItems(user: User): Flowable> = localRepository.getSpecialItems(user) - override fun useSkill(key: String, target: String?, taskId: String): Flowable { - return zipWithLiveUser(apiClient.useSkill(key, target ?: "", taskId)) { response, user -> - response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) - response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) - response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) - response.user?.let { mergeUser(user, it) } - response - } + override suspend fun useSkill(key: String, target: String?, taskId: String): SkillResponse? { + val response = apiClient.useSkill(key, target ?: "", taskId) ?: return null + val user = getLiveUser() ?: return response + response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) + response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) + response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) + response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) + response.user?.let { mergeUser(user, it) } + return response } - override fun useSkill(key: String, target: String?): Flowable { - return zipWithLiveUser(apiClient.useSkill(key, target ?: "")) { response, user -> - response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) - response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) - response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) - response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) - response.user?.let { mergeUser(user, it) } - response - } + override suspend fun useSkill(key: String, target: String?): SkillResponse? { + val response = apiClient.useSkill(key, target ?: "") ?: return null + val user = getLiveUser() ?: return response + response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0) + response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0) + response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0) + response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f) + response.user?.let { mergeUser(user, it) } + return response } override suspend fun disableClasses(): User? = apiClient.disableClasses() @@ -163,20 +158,19 @@ class UserRepositoryImpl( return retrieveUser(false, forced = true) } - override fun unlockPath(customization: Customization): Flowable { + override suspend fun unlockPath(customization: Customization): UnlockResponse? { return unlockPath(customization.path, customization.price ?: 0) } - override fun unlockPath(path: String, price: Int): Flowable { - return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser -> - val user = localRepository.getUnmanagedCopy(copiedUser) - user.preferences = unlockResponse.preferences - user.purchased = unlockResponse.purchased - user.items = unlockResponse.items - user.balance = copiedUser.balance - (price / 4.0) - localRepository.saveUser(copiedUser, false) - unlockResponse - } + override suspend fun unlockPath(path: String, price: Int): UnlockResponse? { + val unlockResponse = apiClient.unlockPath(path) ?: return null + val user = localRepository.getUser(userID).firstOrNull() ?: return unlockResponse + user.preferences = unlockResponse.preferences + user.purchased = unlockResponse.purchased + user.items = unlockResponse.items + user.balance = user.balance - (price / 4.0) + localRepository.saveUser(user, false) + return unlockResponse } override suspend fun runCron() { @@ -204,9 +198,10 @@ class UserRepositoryImpl( return apiClient.changeCustomDayStart(updateObject) } - override fun updateLanguage(languageCode: String): Flowable { - return updateUser("preferences.language", languageCode) - .doOnNext { apiClient.setLanguageCode(languageCode) } + override suspend fun updateLanguage(languageCode: String): User? { + val user = updateUser("preferences.language", languageCode) + apiClient.setLanguageCode(languageCode) + return user } override suspend fun resetAccount(): User? { @@ -220,21 +215,18 @@ class UserRepositoryImpl( override fun sendPasswordResetEmail(email: String): Flowable = apiClient.sendPasswordResetEmail(email) - override fun updateLoginName(newLoginName: String, password: String?): Maybe { - return ( - if (password != null && password.isNotEmpty()) { - apiClient.updateLoginName(newLoginName.trim(), password.trim()) - } else { - apiClient.updateUsername(newLoginName.trim()) - } - ).flatMapMaybe { localRepository.getUserFlowable(userID).firstElement() } - .doOnNext { user -> - localRepository.modify(user) { liveUser -> - liveUser.authentication?.localAuthentication?.username = newLoginName - liveUser.flags?.verifiedUsername = true - } - } - .firstElement() + override suspend fun updateLoginName(newLoginName: String, password: String?): User? { + if (password != null && password.isNotEmpty()) { + apiClient.updateLoginName(newLoginName.trim(), password.trim()) + } else { + apiClient.updateUsername(newLoginName.trim()) + } + val user = localRepository.getUser(userID).firstOrNull() ?: return null + localRepository.modify(user) { liveUser -> + liveUser.authentication?.localAuthentication?.username = newLoginName + liveUser.flags?.verifiedUsername = true + } + return user } override fun verifyUsername(username: String): Flowable = apiClient.verifyUsername(username.trim()) @@ -250,7 +242,7 @@ class UserRepositoryImpl( apiClient.updatePassword(oldPassword.trim(), newPassword.trim(), newPasswordConfirmation.trim()) override fun allocatePoint(stat: Attribute): Flowable { - getLiveUser().firstElement().subscribe( + getLiveUserFlowable().firstElement().subscribe( { liveUser -> localRepository.executeTransaction { when (stat) { @@ -324,7 +316,7 @@ class UserRepositoryImpl( } } - override fun useCustomization(type: String, category: String?, identifier: String): Flowable { + override suspend fun useCustomization(type: String, category: String?, identifier: String): User? { if (appConfigManager.enableLocalChanges()) { localRepository.getUserFlowable(userID).firstElement().subscribe( { liveUser -> @@ -401,14 +393,19 @@ class UserRepositoryImpl( return localRepository.getTeamPlan(teamID) } - private fun getLiveUser(): Flowable { + private fun getLiveUserFlowable(): Flowable { return localRepository.getUserFlowable(userID) .map { Optional(localRepository.getLiveObject(it)) } .filterMapEmpty() } + private suspend fun getLiveUser(): User? { + val user = localRepository.getUser(userID).firstOrNull() ?: return null + return localRepository.getLiveObject(user) + } + private fun zipWithLiveUser(flowable: Flowable, mergeFunc: BiFunction): Flowable { - return Flowable.zip(flowable, getLiveUser().firstElement().toFlowable(), mergeFunc) + return Flowable.zip(flowable, getLiveUserFlowable().firstElement().toFlowable(), mergeFunc) } private fun mergeUser(oldUser: User?, newUser: User): User { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt index 3d37970c9..04dbb8e90 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt @@ -10,11 +10,12 @@ import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus import io.reactivex.rxjava3.core.Flowable +import io.realm.RealmResults import kotlinx.coroutines.flow.Flow interface UserLocalRepository : BaseLocalRepository { - fun getTutorialSteps(): Flowable> + suspend fun getTutorialSteps(): Flow> fun getUser(userID: String): Flow fun getUserFlowable(userID: String): Flowable 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 af919b312..501efe7b8 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 @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.extensions.filterMap import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.QuestAchievement @@ -12,6 +11,7 @@ import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.models.user.UserQuestStatus import hu.akarnokd.rxjava3.bridge.RxJavaBridge import io.reactivex.rxjava3.core.Flowable import io.realm.Realm @@ -21,7 +21,8 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), UserLocalRepository { +class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), + UserLocalRepository { override fun getUserQuestStatus(userID: String): Flowable { return getUserFlowable(userID) .map { it.party?.id ?: "" } @@ -39,7 +40,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), .map { when { it.quest?.members?.find { questMember -> questMember.key == userID } === null -> UserQuestStatus.NO_QUEST - it.quest?.progress?.collect?.isNotEmpty() ?: false -> UserQuestStatus.QUEST_COLLECT + it.quest?.progress?.collect?.isNotEmpty() + ?: false -> UserQuestStatus.QUEST_COLLECT it.quest?.progress?.hp ?: 0.0 > 0.0 -> UserQuestStatus.QUEST_BOSS else -> UserQuestStatus.QUEST_UNKNOWN } @@ -48,25 +50,24 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), override fun getAchievements(): Flow> { return realm.where(Achievement::class.java) - .sort("index") - .findAll() - .toFlow() - .filter { it.isLoaded } + .sort("index") + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getQuestAchievements(userID: String): Flow> { return realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .toFlow() - .filter { it.isLoaded && it.size > 0 } - .map { it.first()?.questAchievements ?: emptyList() } + .equalTo("id", userID) + .findAll() + .toFlow() + .filter { it.isLoaded && it.size > 0 } + .map { it.first()?.questAchievements ?: emptyList() } } - override fun getTutorialSteps(): Flowable> = RxJavaBridge.toV3Flowable( - realm.where(TutorialStep::class.java).findAll().asFlowable() + override suspend fun getTutorialSteps() = + realm.where(TutorialStep::class.java).findAll().toFlow() .filter { it.isLoaded }.map { it } - ) override fun getUser(userID: String): Flow { if (realm.isClosed) return emptyFlow() @@ -82,11 +83,11 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), if (realm.isClosed) return Flowable.empty() return RxJavaBridge.toV3Flowable( realm.where(User::class.java) - .equalTo("id", userID) - .findAll() - .asFlowable() - .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } - .map { users -> users.first() }) + .equalTo("id", userID) + .findAll() + .asFlowable() + .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() } + .map { users -> users.first() }) } override fun saveUser(user: User, overrideExisting: Boolean) { @@ -127,10 +128,10 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), override fun getTeamPlans(userID: String): Flow> { return realm.where(TeamPlan::class.java) - .equalTo("userID", userID) - .findAll() - .toFlow() - .filter { it.isLoaded } + .equalTo("userID", userID) + .findAll() + .toFlow() + .filter { it.isLoaded } } override fun getTeamPlan(teamID: String): Flowable { @@ -146,7 +147,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), } override fun getSkills(user: User): Flowable> { - val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass + val habitClass = + if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass return RxJavaBridge.toV3Flowable( realm.where(Skill::class.java) .equalTo("habitClass", habitClass) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt index 2e1f573aa..91f3821f4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ExceptionHandler.kt @@ -5,6 +5,8 @@ import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.proxy.AnalyticsManager import io.reactivex.rxjava3.functions.Consumer import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import okhttp3.internal.http2.ConnectionShutdownException import retrofit2.HttpException import java.io.EOFException @@ -53,3 +55,7 @@ class ExceptionHandler { } } } + +fun CoroutineScope.launchCatching(function: suspend CoroutineScope.() -> Unit) { + launch((ExceptionHandler.coroutine()), block = function) +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index 19fed52b9..2cd447a1e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -501,6 +501,10 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { return daysOfMonth } + fun canEdit(userID: String): Boolean { + return true + } + companion object CREATOR : Parcelable.Creator { override fun createFromParcel(source: Parcel): Task = Task(source) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt index c09c999f1..f18b327f0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt @@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.helpers.AdHandler import com.habitrpg.android.habitica.helpers.AdType import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler -import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.ads.AdButton import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog @@ -26,6 +25,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.Animations import com.plattysoft.leonids.ParticleSystem +import kotlinx.coroutines.launch import java.util.Locale import javax.inject.Inject @@ -80,22 +80,20 @@ class ArmoireActivity : BaseActivity() { Log.d("AdHandler", "Giving Armoire") val user = userViewModel.user.value ?: return@AdHandler val currentGold = user.stats?.gp ?: return@AdHandler - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("stats.gp", currentGold + 100) - .flatMap { inventoryRepository.buyItem(user, "armoire", 100.0, 1) } - .subscribe({ - configure( - it.armoire["type"] ?: "", - it.armoire["dropKey"] ?: "", - it.armoire["dropText"] ?: "", - it.armoire["value"] ?: "" - ) - binding.adButton.state = AdButton.State.UNAVAILABLE - binding.adButton.visibility = View.INVISIBLE - hasAnimatedChanges = false - gold = null - }, ExceptionHandler.rx()) - ) + val buyResponse = inventoryRepository.buyItem(user, "armoire", 100.0, 1) ?: return@launch + configure( + buyResponse.armoire["type"] ?: "", + buyResponse.armoire["dropKey"] ?: "", + buyResponse.armoire["dropText"] ?: "", + buyResponse.armoire["value"] ?: "" + ) + binding.adButton.state = AdButton.State.UNAVAILABLE + binding.adButton.visibility = View.INVISIBLE + hasAnimatedChanges = false + gold = null + } } handler.prepare { if (it && binding.adButton.state == AdButton.State.LOADING) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt index 4e035a944..64b90d66b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt @@ -57,11 +57,10 @@ class DeathActivity: BaseActivity() { return@AdHandler } Log.d("AdHandler", "Reviving user") - compositeSubscription.add( - userRepository.updateUser("stats.hp", 1).subscribe({ - finish() - }, ExceptionHandler.rx()) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("stats.hp", 1) + finish() + } } handler.prepare { if (it && binding.adButton.state == AdButton.State.LOADING) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index 7f078676c..ec94f2fc8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -209,17 +209,10 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { additionalData["status"] = "completed" AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) - compositeSubscription.add( - userRepository.updateUser("flags.welcomed", true).subscribe( - { - if (!compositeSubscription.isDisposed) { - compositeSubscription.dispose() - } - startMainActivity() - }, - ExceptionHandler.rx() - ) - ) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("flags.welcomed", true) + startMainActivity() + } return } this.user = user @@ -238,11 +231,10 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { } private fun confirmNames(displayName: String, username: String) { - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("profile.name", displayName) - .flatMap { userRepository.updateLoginName(username).toFlowable() } - .subscribe({ }, ExceptionHandler.rx()) - ) + userRepository.updateLoginName(username) + } } private inner class ViewPageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT), IconPagerAdapter { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt index fde4b03c7..31ac9393d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt @@ -114,13 +114,12 @@ class VerifyUsernameActivity : BaseActivity() { private fun confirmNames() { binding.confirmUsernameButton.isClickable = false - compositeSubscription.add( + lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser("profile.name", binding.displayNameEditText.text.toString()) - .flatMap { userRepository.updateLoginName(binding.usernameEditText.text.toString()).toFlowable() } - .doOnComplete { showConfirmationAndFinish() } - .doOnEach { binding.confirmUsernameButton.isClickable = true } - .subscribe({ }, ExceptionHandler.rx()) - ) + userRepository.updateLoginName(binding.usernameEditText.text.toString()) + showConfirmationAndFinish() + binding.confirmUsernameButton.isClickable = true + } } private fun showConfirmationAndFinish() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt index 3238e6245..9b44cfe63 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt @@ -18,11 +18,8 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.loadImage -import com.habitrpg.shared.habitica.models.Avatar import com.habitrpg.common.habitica.views.AvatarView -import io.reactivex.rxjava3.core.BackpressureStrategy -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.subjects.PublishSubject +import com.habitrpg.shared.habitica.models.Avatar import java.util.Date import java.util.EnumMap import kotlin.math.min @@ -46,7 +43,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler var ownedCustomizations: List = listOf() private var pinnedItemKeys: List = ArrayList() - private val selectCustomizationEvents = PublishSubject.create() + var onCustomizationSelected: ((Customization) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { return when (viewType) { @@ -152,10 +149,6 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler this.notifyDataSetChanged() } - fun getSelectCustomizationEvents(): Flowable { - return selectCustomizationEvents.toFlowable(BackpressureStrategy.DROP) - } - fun setPinnedItemKeys(pinnedItemKeys: List) { this.pinnedItemKeys = pinnedItemKeys if (customizationList.size > 0) this.notifyDataSetChanged() @@ -234,7 +227,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler alert.setMessage(customization?.notes) alert.addButton(R.string.equip, true) { _, _ -> customization?.let { - selectCustomizationEvents.onNext(it) + onCustomizationSelected?.invoke(it) } } alert.addButton(R.string.close, false) { _, _ -> @@ -243,7 +236,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler alert.show() } else { customization?.let { - selectCustomizationEvents.onNext(it) + onCustomizationSelected?.invoke(it) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt index afcd17483..2eea727e3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/setup/CustomizationSetupAdapter.kt @@ -24,8 +24,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val equipGearEvents: Flowable = equipGearEventSubject.toFlowable(BackpressureStrategy.DROP) - private val updateUserEventsSubject = PublishSubject.create>() - val updateUserEvents: Flowable> = updateUserEventsSubject.toFlowable(BackpressureStrategy.DROP) + var onUpdateUser: ((Map) -> Unit)? = null fun setCustomizationList(newCustomizationList: List) { this.customizationList = newCustomizationList @@ -128,7 +127,7 @@ internal class CustomizationSetupAdapter : RecyclerView.Adapter() val updatePath = "preferences." + selectedCustomization.path updateData[updatePath] = selectedCustomization.key - updateUserEventsSubject.onNext(updateData) + onUpdateUser?.invoke(updateData) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt index 748d9c160..ff54e804f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt @@ -9,11 +9,13 @@ import android.webkit.WebChromeClient import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentNewsBinding -import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import kotlinx.coroutines.launch class NewsFragment : BaseMainFragment() { @@ -75,6 +77,8 @@ class NewsFragment : BaseMainFragment() { override fun onResume() { super.onResume() - compositeSubscription.add(userRepository.updateUser("flags.newStuff", false).subscribeWithErrorHandler({})) + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.updateUser("flags.newStuff", false) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt index 237fbc1b8..dc955b258 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt @@ -5,23 +5,25 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentStatsBinding import com.habitrpg.android.habitica.extensions.addOkButton -import com.habitrpg.common.habitica.extensions.getThemeColor import com.habitrpg.android.habitica.extensions.setScaledPadding import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.UserStatComputer -import com.habitrpg.shared.habitica.models.tasks.Attribute +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog +import com.habitrpg.common.habitica.extensions.getThemeColor +import com.habitrpg.shared.habitica.models.tasks.Attribute import javax.inject.Inject import kotlin.math.min @@ -131,8 +133,9 @@ class StatsFragment : BaseMainFragment() { } binding?.automaticAllocationSwitch?.setOnCheckedChangeListener { _, isChecked -> - userRepository.updateUser("preferences.automaticAllocation", isChecked) - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.automaticAllocation", isChecked) + } } binding?.strengthStatsView?.allocateAction = { allocatePoint(Attribute.STRENGTH) } @@ -159,12 +162,12 @@ class StatsFragment : BaseMainFragment() { } private fun changeAutoAllocationMode(allocationMode: String) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser( "preferences.allocationMode", allocationMode - ).subscribe({}, ExceptionHandler.rx()) - ) + ) + } binding?.distributeEvenlyButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_FLAT binding?.distributeClassButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_CLASSBASED binding?.distributeTaskButton?.isChecked = allocationMode == Stats.AUTO_ALLOCATE_TASKBASED diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt index 46aeedfca..f09793524 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt @@ -26,6 +26,7 @@ import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBin import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.CustomizationFilter import com.habitrpg.android.habitica.models.inventory.Customization import com.habitrpg.android.habitica.models.user.OwnedCustomization @@ -79,18 +80,20 @@ class AvatarCustomizationFragment : savedInstanceState: Bundle? ): View? { showsBackButton = true - compositeSubscription.add( - adapter.getSelectCustomizationEvents() - .flatMap { customization -> + adapter.onCustomizationSelected = { customization -> + lifecycleScope.launchCatching { if (customization.type == "background") { userRepository.unlockPath(customization) - //TODO: .flatMap { userRepository.retrieveUser(false, true, true) } + userRepository.retrieveUser(false, true, true) } else { - userRepository.useCustomization(customization.type ?: "", customization.category, customization.identifier ?: "") + userRepository.useCustomization( + customization.type ?: "", + customization.category, + customization.identifier ?: "" + ) } } - .subscribe({ }, ExceptionHandler.rx()) - ) + } compositeSubscription.add( this.inventoryRepository.getInAppRewards() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt index 0b0e62358..11ae5b17d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt @@ -20,11 +20,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentComposeScrollingBinding -import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel @@ -89,10 +90,9 @@ class AvatarOverviewFragment : BaseMainFragment override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { val newSize: String = if (position == 0) "slim" else "broad" - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("preferences.size", newSize) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } override fun onNothingSelected(parent: AdapterView<*>) { /* no-on */ diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index fca4bbc7d..345df8dda 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -19,8 +19,9 @@ import com.habitrpg.android.habitica.databinding.FragmentItemsBinding import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.observeOnce import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler -import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food @@ -28,7 +29,6 @@ import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.Item import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.inventory.SpecialItem -import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User @@ -44,7 +44,6 @@ import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.helpers.EmptyItem -import io.reactivex.rxjava3.core.Flowable import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -322,16 +321,10 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL if (specialItem == null || memberID == null) { return } - - val observable: Flowable = + lifecycleScope.launchCatching { userRepository.useSkill(specialItem.key, specialItem.target, memberID) - - compositeSubscription.add( - observable.subscribe( - { this.displaySpecialItemResult(specialItem) }, - ExceptionHandler.rx() - ) - ) + displaySpecialItemResult(specialItem) + } } private fun displaySpecialItemResult(specialItem: SpecialItem?) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index d76d6d85f..09e002eae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -27,6 +27,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed @@ -256,8 +257,9 @@ class AccountPreferenceFragment : private fun updateUser(path: String, value: String?, title: String) { showSingleEntryDialog(value, title) { if (value != it) { - userRepository.updateUser(path, it ?: "") - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser(path, it ?: "") + } } } } @@ -388,8 +390,9 @@ class AccountPreferenceFragment : private fun showLoginNameDialog() { showSingleEntryDialog(user?.username, getString(R.string.username)) { - userRepository.updateLoginName(it ?: "") - .subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateLoginName(it ?: "") + } } } @@ -478,8 +481,11 @@ class AccountPreferenceFragment : dialog.setTitle(R.string.confirm_username_title) dialog.setMessage(R.string.confirm_username_description) dialog.addButton(R.string.confirm, true) { _, _ -> - userRepository.updateLoginName(user?.authentication?.localAuthentication?.username ?: "") - .subscribe({ }, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateLoginName( + user?.authentication?.localAuthentication?.username ?: "" + ) + } } dialog.addCancelButton() dialog.show() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt index 8a8ed8b59..a1e6f0603 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.ui.fragments.preferences import android.content.SharedPreferences import android.os.Bundle +import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -73,7 +74,12 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.emailNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.emailNotifications.$pathKey", + sharedPreferences.getBoolean(key, false) + ) + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index 035b054f5..8191c4983 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.helpers.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.prefs.TimePreference @@ -207,7 +208,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "usePushNotifications" -> { val usePushNotifications = sharedPreferences.getBoolean(key, true) pushNotificationsPreference?.isEnabled = usePushNotifications - userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications).subscribe() + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications) + } if (usePushNotifications) { pushNotificationManager.addPushDeviceUsingStoredToken() } else { @@ -217,7 +220,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "useEmails" -> { val useEmailNotifications = sharedPreferences.getBoolean(key, true) emailNotificationsPreference?.isEnabled = useEmailNotifications - userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", !useEmailNotifications).subscribe() + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", !useEmailNotifications) + } } "cds_time" -> { val timeval = sharedPreferences.getString("cds_time", "0") ?: "0" @@ -238,10 +243,10 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare if (user?.preferences?.language == languageHelper.languageCode) { return } - - userRepository.updateLanguage(languageHelper.languageCode ?: "en") - .subscribe({ reloadContent(false) }, ExceptionHandler.rx()) - + lifecycleScope.launchCatching { + userRepository.updateLanguage(languageHelper.languageCode ?: "en") + reloadContent(false) + } val intent = Intent(activity, MainActivity::class.java) this.startActivity(intent) activity?.finishAffinity() @@ -249,10 +254,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "audioTheme" -> { val newAudioTheme = sharedPreferences.getString(key, "off") if (newAudioTheme != null) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("preferences.sound", newAudioTheme) - .subscribe({ }, ExceptionHandler.rx()) - ) + } soundManager.soundTheme = newAudioTheme soundManager.preloadAllFiles() } @@ -265,8 +269,12 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare val activity = activity as? PrefsActivity ?: return activity.reload() } - "dailyDueDefaultView" -> userRepository.updateUser("preferences.dailyDueDefaultView", sharedPreferences.getBoolean(key, false)) - .subscribe({ }, ExceptionHandler.rx()) + "dailyDueDefaultView" -> lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.dailyDueDefaultView", + sharedPreferences.getBoolean(key, false) + ) + } "server_url" -> { apiClient.updateServerUrl(sharedPreferences.getString(key, "")) findPreference(key)?.summary = sharedPreferences.getString(key, "") @@ -282,10 +290,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare "disablePMs" -> { val isDisabled = sharedPreferences.getBoolean("disablePMs", false) if (user?.inbox?.optOut != isDisabled) { - compositeSubscription.add( + lifecycleScope.launchCatching { userRepository.updateUser("inbox.optOut", isDisabled) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } "launch_screen" -> { @@ -390,7 +397,12 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } else if (newValue == false && currentIds.contains(team.id)) { currentIds.remove(team.id) } - userRepository.updateUser("preferences.tasks.mirrorGroupTasks", currentIds).subscribe({}, ExceptionHandler.rx()) + lifecycleScope.launchCatching { + userRepository.updateUser( + "preferences.tasks.mirrorGroupTasks", + currentIds + ) + } true } groupCategory?.addPreference(newPreference) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt index 07a654ab3..702fc6270 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt @@ -2,9 +2,10 @@ package com.habitrpg.android.habitica.ui.fragments.preferences import android.content.SharedPreferences import android.os.Bundle +import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import com.habitrpg.android.habitica.HabiticaBaseApplication -import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -75,7 +76,9 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr else -> null } if (pathKey != null) { - compositeSubscription.add(userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)).subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + userRepository.updateUser("preferences.pushNotifications.$pathKey", sharedPreferences.getBoolean(key, false)) + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt index b6c57d7e6..31dc320b3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.tabs.TabLayout import com.habitrpg.android.habitica.R @@ -15,6 +16,7 @@ import com.habitrpg.android.habitica.data.SetupCustomizationRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentSetupAvatarBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.SetupCustomization import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.SetupActivity @@ -56,7 +58,11 @@ class AvatarSetupFragment : BaseFragment() { this.adapter = CustomizationSetupAdapter() this.adapter?.userSize = this.user?.preferences?.size ?: "slim" - adapter?.updateUserEvents?.flatMap { userRepository.updateUser(it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } + adapter?.onUpdateUser = { + lifecycleScope.launchCatching { + userRepository.updateUser(it) + } + } adapter?.equipGearEvents?.flatMap { inventoryRepository.equip("equipped", it) }?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) } this.adapter?.user = this.user @@ -185,7 +191,9 @@ class AvatarSetupFragment : BaseFragment() { updateData["preferences.hair.bangs"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_HAIR, SetupCustomizationRepository.SUBCATEGORY_BANGS, user), false) updateData["preferences.hair.flower"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_EXTRAS, SetupCustomizationRepository.SUBCATEGORY_FLOWER, user), true) updateData["preferences.chair"] = chooseRandomKey(customizationRepository.getCustomizations(SetupCustomizationRepository.CATEGORY_EXTRAS, SetupCustomizationRepository.SUBCATEGORY_WHEELCHAIR, user), true) - compositeSubscription.add(userRepository.updateUser(updateData).subscribeWithErrorHandler({})) + lifecycleScope.launchCatching { + userRepository.updateUser(updateData) + } } @Suppress("ReturnCount") @@ -215,4 +223,4 @@ class AvatarSetupFragment : BaseFragment() { params?.marginStart = location[0] + px binding?.customizationDrawer?.binding?.caretView?.layoutParams = params } -} +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index 428f6ef7c..8b6322401 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.databinding.FragmentSkillsBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.responses.SkillResponse import com.habitrpg.android.habitica.models.user.User @@ -161,16 +162,15 @@ class SkillsFragment : BaseMainFragment() { if (skill == null) { return } - val observable: Flowable = if (taskId != null) { - userRepository.useSkill(skill.key, skill.target, taskId) - } else { - userRepository.useSkill(skill.key, skill.target) + lifecycleScope.launchCatching { + val skillResponse = if (taskId != null) { + userRepository.useSkill(skill.key, skill.target, taskId) + } else { + userRepository.useSkill(skill.key, skill.target) + } + if (skillResponse != null) { + displaySkillResult(skill, skillResponse) + } } - compositeSubscription.add( - observable.subscribe( - { skillResponse -> this.displaySkillResult(skill, skillResponse) }, - ExceptionHandler.rx() - ) - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt index 402109bdd..1b1d8c084 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt @@ -6,16 +6,18 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.FAQRepository import com.habitrpg.android.habitica.databinding.FragmentSupportMainBinding import com.habitrpg.android.habitica.helpers.AppConfigManager -import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named @@ -59,9 +61,11 @@ class SupportMainFragment : BaseMainFragment() { } binding?.resetTutorialButton?.setOnClickListener { - userRepository.resetTutorial().subscribe({ + lifecycleScope.launch(ExceptionHandler.coroutine()) { + userRepository.resetTutorial() activity?.showSnackbar(null, null, getString(R.string.tutorial_reset_confirmation), displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS) - }, ExceptionHandler.rx()) + + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index 82854c32a..d646de22e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -15,6 +15,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity @@ -22,7 +23,6 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.shared.habitica.models.tasks.TaskType -import io.reactivex.rxjava3.functions.Consumer import kotlinx.coroutines.launch class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { @@ -35,7 +35,9 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - compositeSubscription.add(inventoryRepository.retrieveInAppRewards().subscribe({ }, ExceptionHandler.rx())) + lifecycleScope.launchCatching { + inventoryRepository.retrieveInAppRewards() + } return super.onCreateView(inflater, container, savedInstanceState) } @@ -97,13 +99,9 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { binding?.refreshLayout?.isRefreshing = true lifecycleScope.launch(ExceptionHandler.coroutine()) { userRepository.retrieveUser(withTasks = true, forced = true) - } - compositeSubscription.add( inventoryRepository.retrieveInAppRewards() - .doOnTerminate { - binding?.refreshLayout?.isRefreshing = false - }.subscribe({ }, ExceptionHandler.rx()) - ) + binding?.refreshLayout?.isRefreshing = false + } } private fun setGridSpanCount(width: Int) { @@ -122,21 +120,19 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { private val cardSelectedResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { - userRepository.useSkill( - selectedCard?.key ?: "", - "member", - it.data?.getStringExtra("member_id") ?: "" - ) - .subscribeWithErrorHandler( - Consumer { - val activity = (activity as? MainActivity) ?: return@Consumer - HabiticaSnackbar.showSnackbar( - activity.snackbarContainer, - context?.getString(R.string.sent_card, selectedCard?.text), - HabiticaSnackbar.SnackbarDisplayType.BLUE - ) - } + lifecycleScope.launchCatching { + userRepository.useSkill( + selectedCard?.key ?: "", + "member", + it.data?.getStringExtra("member_id") ?: "" ) + val activity = (activity as? MainActivity) ?: return@launchCatching + HabiticaSnackbar.showSnackbar( + activity.snackbarContainer, + context?.getString(R.string.sent_card, selectedCard?.text), + HabiticaSnackbar.SnackbarDisplayType.BLUE + ) + } } } 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 50078eceb..e4656357d 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 @@ -2,12 +2,14 @@ package com.habitrpg.android.habitica.ui.viewmodels import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope 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.ExceptionHandler import com.habitrpg.android.habitica.models.user.User import io.reactivex.rxjava3.disposables.CompositeDisposable +import kotlinx.coroutines.launch import javax.inject.Inject abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() { @@ -38,9 +40,8 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() internal val disposable = CompositeDisposable() fun updateUser(path: String, value: Any) { - disposable.add( + viewModelScope.launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index c86726f30..a141c608d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -11,10 +11,12 @@ import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.user.User import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.launch class MainUserViewModel(private val providedUserID: String, val userRepository: UserRepository, val socialRepository: SocialRepository) { @@ -59,16 +61,14 @@ class MainUserViewModel(private val providedUserID: String, val userRepository: internal val disposable = CompositeDisposable() fun updateUser(path: String, value: Any) { - disposable.add( + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.updateUser(path, value) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } fun updateUser(data: Map) { - disposable.add( + MainScope().launch(ExceptionHandler.coroutine()) { userRepository.updateUser(data) - .subscribe({ }, ExceptionHandler.rx()) - ) + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt index 1652ba27f..0cd43b1da 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt @@ -6,12 +6,14 @@ import android.view.LayoutInflater import android.view.View import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.ExceptionHandler +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg @@ -22,7 +24,6 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.common.habitica.extensions.DataBindingUtils import com.habitrpg.common.habitica.extensions.loadImage import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import java.util.Locale import javax.inject.Inject @@ -140,21 +141,16 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { val activity = (getActivity() as? MainActivity) ?: return@addButton val thisPotion = potion ?: return@addButton val thisEgg = egg ?: return@addButton - var observable: Flowable = Flowable.just("") - if (!hasEgg) { - observable = observable.flatMap { activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1) } + lifecycleScope.launchCatching { + if (!hasEgg) { + activity.inventoryRepository.purchaseItem("eggs", thisEgg.key, 1) + } + if (!hasPotion) { + activity.inventoryRepository.purchaseItem("hatchingPotions", thisPotion.key, 1) + } + activity.userRepository.retrieveUser(true, forced = true) + hatchPet(thisPotion, thisEgg) } - if (!hasPotion) { - observable = observable.flatMap { activity.inventoryRepository.purchaseItem("hatchingPotions", thisPotion.key, 1) } - } - observable - .subscribe( - { - // TODO: activity.userRepository.retrieveUser(true, forced = true) - hatchPet(thisPotion, thisEgg) - }, - ExceptionHandler.rx() - ) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt index 953bb8be7..b17cbb98d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt @@ -10,6 +10,7 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R @@ -23,6 +24,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ExceptionHandler import com.habitrpg.android.habitica.helpers.HapticFeedbackManager import com.habitrpg.android.habitica.helpers.MainNavigationController +import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.models.user.OwnedItem @@ -38,7 +40,6 @@ import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientG import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientSubscriberGemsDialog import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -254,7 +255,12 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop priceLabel = buyButton.findViewById(R.id.priceLabel) priceLabel.animationDuration = 0L buyLabel = buyButton.findViewById(R.id.buy_label) - pinButton.setOnClickListener { inventoryRepository.togglePinnedItem(shopItem).subscribe({ isPinned = !this.isPinned }, ExceptionHandler.rx()) } + pinButton.setOnClickListener { + lifecycleScope.launchCatching { + inventoryRepository.togglePinnedItem(shopItem) + isPinned = !isPinned + } + } shopItem = item @@ -357,30 +363,31 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop ) HapticFeedbackManager.tap(contentView) val snackbarText = arrayOf("") - val observable: Flowable + val observable: (suspend () -> Unit) if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") { observable = if (shopItem.purchaseType == "gear") { - inventoryRepository.purchaseMysterySet(shopItem.key).cast(Any::class.java) + { inventoryRepository.purchaseMysterySet(shopItem.key) } } else { - inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key).cast(Any::class.java) + { inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key) } } - // TODO: } else if (shopItem.purchaseType == "fortify") { - // observable = userRepository.reroll().cast(Any::class.java) + } else if (shopItem.purchaseType == "fortify") { + observable = { userRepository.reroll() } } else if (shopItem.purchaseType == "quests" && shopItem.currency == "gold") { - observable = inventoryRepository.purchaseQuest(shopItem.key).cast(Any::class.java) + observable = { inventoryRepository.purchaseQuest(shopItem.key) } } else if (shopItem.purchaseType == "debuffPotion") { - observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java) + observable = { userRepository.useSkill(shopItem.key, null) } } else if (shopItem.purchaseType == "customization" || shopItem.purchaseType == "background" || shopItem.purchaseType == "backgrounds" || shopItem.purchaseType == "customizationSet") { - observable = userRepository.unlockPath(item.unlockPath ?: "${item.pinType}.${item.key}" ?: "", item.value).cast(Any::class.java) + observable = { userRepository.unlockPath(item.unlockPath ?: "${item.pinType}.${item.key}" ?: "", item.value) } } else if (shopItem.purchaseType == "debuffPotion") { - observable = userRepository.useSkill(shopItem.key, null).cast(Any::class.java) + observable = { userRepository.useSkill(shopItem.key, null) } } else if (shopItem.purchaseType == "card") { purchaseCardAction?.invoke(shopItem) dismiss() return } else if ("gold" == shopItem.currency && "gem" != shopItem.key) { - observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse -> - if (shopItem.key == "armoire" && configManager.enableNewArmoire()) { + observable = { + val buyResponse = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity) + if (shopItem.key == "armoire" && configManager.enableNewArmoire() && buyResponse != null) { MainNavigationController.navigate( R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity( @@ -391,45 +398,34 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop ).arguments ) } - buyResponse } } else { - observable = inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity).cast(Any::class.java) + observable = { inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity) } } - val subscription = observable - .doOnNext { - val text = if (snackbarText[0].isNotEmpty()) { - snackbarText[0] - } else { - context.getString(R.string.successful_purchase, shopItem.text) - } - val rightTextColor = when (item.currency) { - "gold" -> ContextCompat.getColor(context, R.color.text_yellow) - "gems" -> ContextCompat.getColor(context, R.color.text_green) - "hourglasses" -> ContextCompat.getColor(context, R.color.text_brand) - else -> 0 - } - ((application?.currentActivity?.get() ?: getActivity() ?: ownerActivity) as? SnackbarActivity)?.showSnackbar( - content = text, - rightIcon = priceLabel.compoundDrawables[0], - rightTextColor = rightTextColor, - rightText = "-" + priceLabel.text - ) + lifecycleScope.launchCatching { + observable() + val text = if (snackbarText[0].isNotEmpty()) { + snackbarText[0] + } else { + context.getString(R.string.successful_purchase, shopItem.text) } - // TODO: .flatMap { userRepository.retrieveUser(withTasks = false, forced = true) } - .flatMap { inventoryRepository.retrieveInAppRewards() } - .subscribe({ - if (item.isTypeGear || item.currency == "hourglasses") { - onGearPurchased?.invoke(item) - } - }) { throwable -> - if (throwable.javaClass.isAssignableFrom(retrofit2.HttpException::class.java)) { - val error = throwable as retrofit2.HttpException - if (error.code() == 401 && shopItem.currency == "gems") { - MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false))) - } - } + val rightTextColor = when (item.currency) { + "gold" -> ContextCompat.getColor(context, R.color.text_yellow) + "gems" -> ContextCompat.getColor(context, R.color.text_green) + "hourglasses" -> ContextCompat.getColor(context, R.color.text_brand) + else -> 0 } + ((application?.currentActivity?.get() ?: getActivity() ?: ownerActivity) as? SnackbarActivity)?.showSnackbar( + content = text, + rightIcon = priceLabel.compoundDrawables[0], + rightTextColor = rightTextColor, + rightText = "-" + priceLabel.text + ) + inventoryRepository.retrieveInAppRewards() + if (item.isTypeGear || item.currency == "hourglasses") { + onGearPurchased?.invoke(item) + } + } } private fun displayPurchaseConfirmationDialog(quantity: Int) { diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index f06786270..5ac5eb01e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -21,7 +21,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("io.realm.kotlin:library-base:1.0.2") } } diff --git a/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt b/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt index eaaa0c50a..fa2589ee1 100644 --- a/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt +++ b/shared/src/androidMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt @@ -14,6 +14,10 @@ actual class PlatformLogger actual constructor() { Log.i(tag, message) } + actual fun logWarning(tag: String, message: String) { + Log.w(tag, message) + } + actual fun logError(tag: String, message: String) { Log.e(tag, message) } diff --git a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt index b79f07723..da6de266c 100644 --- a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt +++ b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/Logger.kt @@ -5,12 +5,13 @@ expect class PlatformLogger() { fun logDebug(tag: String, message: String) fun logInfo(tag: String, message: String) + fun logWarning(tag: String, message: String) fun logError(tag: String, message: String) fun logError(tag: String, message: String, exception: Throwable) } enum class LogLevel { - ERROR, INFO, DEBUG + ERROR, INFO, WARNING, DEBUG } class HLogger { @@ -26,6 +27,7 @@ class HLogger { when (level) { LogLevel.ERROR -> platformLogger.logError(tag, message) LogLevel.INFO -> platformLogger.logInfo(tag, message) + LogLevel.WARNING -> platformLogger.logWarning(tag, message) LogLevel.DEBUG -> platformLogger.logDebug(tag, message) } } diff --git a/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt b/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt index 84871b48d..e30cc6c03 100644 --- a/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt +++ b/shared/src/iosMain/kotlin/com/habitrpg/shared/habitica/PlatformLogger.kt @@ -5,18 +5,22 @@ actual class PlatformLogger { get() = true actual fun logDebug(tag: String, message: String) { - println("[DEBUG] $tag: $message") + println("[🟢] $tag: $message") } actual fun logInfo(tag: String, message: String) { - println("[INFO] $tag: $message") + println("[🟡] $tag: $message") + } + + actual fun logWarning(tag: String, message: String) { + println("[🟠] $tag: $message") } actual fun logError(tag: String, message: String) { - println("[ERROR] $tag: $message") + println("[🔴] $tag: $message") } actual fun logError(tag: String, message: String, exception: Throwable) { - println("[ERROR] $tag: $message\n${exception.getStackTrace().joinToString("\n")}") + println("[🔴] $tag: $message\n${exception.getStackTrace().joinToString("\n")}") } } \ No newline at end of file